TypeScript+recomposeでpropsを伝達するときにぶつかった問題などについて

なんかパッと出てこなかったのでメモ

簡単に以下の画像のようなアプリケーションを作成してみます。

デフォルトの数字を親から受け取り、ボタンを押してインクリメントしたりデクリメントしたりリセットしたりするようなものです。
これをrecomposeを使って実装してみます。

recomposeってなに?って方は、以下の記事も参考にしてみてください。
【参考】ReactNativeでHoCとRecomposeを使う | mrsekutの備忘録

問題その1 親コンポーネントのpropsを子コンポーネントに伝達する方法がわからない

AtomicDesignの設計で組んでいくと、親から子へpropsを伝搬させることが多々あります。
そこで、recomposeと組み合わせたときにも親から子へ流す方法をしる必要がありました。

今回の場合は、以下のように親から受け取ったdefaultNumを子コンポーネントで表示させる方法です。

解決策

以下のようなめちゃくちゃシンプルな親子のコンポーネントを考えます。

親コンポーネント

子コンポーネント

上の例ではrecomposeを使う意味はまったくないですが、例のために仕方なくです・・。
大事なのは、CounterBoardPresenterでいつもと同じように({ defaultNum })でpropsを受け取っている部分です。
知ってしまえばなんてことないけど、最初はここはpropsと書くべきなのか、defaultNumと書くべきなのかなどで詰まったりしました。

また、子コンポーネントで受け取る引数は()の中ではなく、({})の中に書きます。
こうすることで、指定した名前のprops単体を受け取ることができます。

少し冗長な書き方をするならば、 以下のような書き方でも同じです。

【参考】分割代入 – JavaScript | MDN

問題その2 TypeScriptを使っていると親の方で「子でそれを受け取るやつがないぞ」と言われる

問題その1は解消したのも束の間、compose関数内にちゃんとロジックを書いていくと、以下のようなエラーに遭遇しました。
Property 'defaultNum' does not exist on type 'IntrinsicAttributes &; IntrinsicClassAttributes<Component<{}, ComponentState, any>> & Readonly<{ c...'.

これは、親のParentComponentの方で起こっているエラーです。
親ファイルの中では、defaultNum={5}というふうにdefaultNumを渡しているけど、子の方ではそれを受け取る記述がないぞと言われています。

解決策

原因は型です。
recomposeのAPIに型を与えていきます。
以下のコードは型を全く書いていない、エラー出まくりのものです。
簡単のため、withState内で定義している数字の初期値は親propsのものではなく、定数の5としています。(理由は後述)
なので、下のコードではボタンを押しても表示されている数字に変化はありません。

これらに適切に、型を与えていきます。
今回、型を与えるべきものに以下の4つがあります。

  • compose
  • withState
  • withHandler
  • CounterBoardPresenter

エラーの原因はcomposeに型が与えられていないからです。

atomのパワーを借りて、型を確認してみます。ちなみにこれは型定義ファイルを直接読みに行っても同じです。

こんなふうに表示されました。

どうやら、「TInner」と「TOutter」に当たる型を定義する必要があるようです。
「TOutter」というのは、outer componentに対する型。
つまり、ここでは、「子コンポーネント」と呼んでいる、CounterBoardPresenterの型のこと。

「TInnerと」いうのは、iner componentに対する型。
つまり、recomposeを使用して作成されたすべてのprops。
ここでは、withStateとwithHandlerを使っており、その中で、counterという変数や、incrementという関数などを使用していますが、これらの型のことです。

なので、以上を踏まえて型を定義していきます。

型を定義する

まずは、CounterBoardPresenterに対する型、ComponentPropsインターフェースを定義します。
親から受け取るpropsなので、今回はdefaultNumのみです。

次に、withStateに対する型。WithStatePropsインターフェースです。

少しややこしいですが、updateCounterの型は、「『number型を引数にとり、number型を返す関数』を引数にとり、void型を返す関数」という意味を示しています。

そして同様にして、withHandlerに対する型を定義していきます。

最後にこれらを組み合わせて、先程のTInnerとTOutterに対応する型を作成します。

型を加える

型の準備ができたので、適用していきます。
まず、withState関数に型を加えてやります。

そしてwithHandler関数にも加えます。

以上を組み合わせると以下のようになります。

以下の質問を見つけてこの解に行き着きました。

【参考】reactjs – Property does not exist on React component when defined with recompose – Stack Overflow

問題その3 recomposeのcompose内で親propsを使う方法

先程は、withState内で、stateの初期値を5と設定しましたが、
ここに親から受け取ったpropsである、defaultNumの値を入れたいとします。
以下のコードは動きませんが、直感的に書くとこんな感じです。

また、withStateの中のみでなく、withHandlerの中のreset関数の中でも必要です。

解決策

withState

こんなふうにしてやります。

詳しくは後述しますが、withStateは第3引数に変数もしくは関数をセットできますが、関数の場合、この関数の引数に親のpropsが入るように実装されているからです。

withHandler

こんなふうにして渡してやります。

こちらも、ほとんど同じ理由です。

問題その4 withHandlerがよくわからない

よくわかないのは、内部実装がよくわかってないからです。
折角の機会ですので、内部のコードを読んでみます。
と、思ったのですが、解説を書いてみるとめちゃくちゃ長くなってしまったので、別記事にしました。
下記終わり次第、ここにリンクを貼ります。

コメントを残す