props のバケツリレーって何が悪いんだっけ
#React やってて、props のバケツリレーを何か嫌がる人たまにいるんだけど、自分は props のバケツリレーそのものをそんなに悪いと思ったことがない。 「バケツリレーがつらい」ように見えるコンポーネントの大半はそもそも props の設計がおかしい場合が多く、本当の問題はそっちにあると思っている。
たとえば、次のようなバケツリレーはつらいかもしれない。ここでいう Body はサイドバーとしてフォロワーの一覧を表示し、メインコンテンツとしてフィード一覧を表示するみたいなものを想像して欲しい。フォロワー一覧の中で使う props とフィード一覧で使う props を混ぜて1つの親に渡している状況だ。
code:typescript
<Body
logo="/logo.svg"
followers={followers} // 実はサイドバーの中でだけ使う
feeds={feeds}
isListview={isListview} // 実はフィード一覧の中でだけ使う
track={sendToGa}
onSelectFeed={handleSelectFeed}
onFollowerClick={handleFollowerClick} // 実はサイドバーの中だけで……
/>
これは「バケツリレーが辛い」と言うより、「props の内容がコンポーネント名にあってないのが辛い」というべきなんだと思う。
(※ 追記: この「props の内容がコンポーネント名にあってない」をもうちょっと言語化すると「不要な親子の結合関係を生んでしまう」とかになりそう。責務が大きくなることと結合関係がおかしくなることは関連がありそうだし。
なんでそういう props を設計してしまうかと言うと、props に渡していいもののイメージが狭いというか、せいぜい数字・文字列・配列・関数……以外のものも props にできるという発想がないことに起因している(大体の場合はそれで良いとはいえ)。
こういう時に考えるべきことは、たとえば children を有効活用するとか、props として JSX やレンダー関数を渡すこと( render props は以前よりは廃れたが今も僅かに使いどころがある )であり、必ずしも「バケツリレーがつらいので Context 使います」といったことではない。
code:typescript
<TrackableProvider track={sendToGa}> // tracking は流石に横断的関心なので Context でいいかなー
<Body // Body の 持つ props として logo、sidebar、content がある、なら意味がわかる
logo="./logo.svg"
sidebar={
// followers と onFollowerClick は FollowerList 専用、それはそうだよね
<FollowerList
followers={followers}
onFollowerClick={onFollowerClick}
/>
}
content={
<FeedList feeds={feeds} isListview={isListview} onSelectFeed={handleSelectFeed} />
}
/>
</TrackableProvider>
見た目が気持ち悪い場合は children を使うとマシになることが多い。
code:typescript
<TrackableProvider track={sendToGa}>
<Body
logo="./logo.svg"
sidebar={<FollowerList followers={followers} onFollowerClick={onFollowerClick} />}
<FeedList feeds={feeds} isListview={isListview} onSelect={handleSelectFeed} />
</Body>
</TrackableProvider>
code:typescript
// で、Body の Props 定義はこれだけ
interface Props {
logo: string
sidebar: React.ReactNode
children: React.ReactNode
}
これだけ見ると利用側の記述量は増えてるが、コンポーネント名と合わない意味不明な props はなくなったと思う(おまけに Body の sidebar がぜんぜん違う内容になるケースに対応しやすくなった)。
みなさんが「バケツリレーが辛い」という時、本当に解決したいのは実はこっちではないか?という視点は持ったほうが良い。
------
もちろん、こういう対策をとってもなお 2 つ下の孫要素に props を渡すことはなくならないかもしれない(たとえば Body を利用しているコンポーネントは feeds を props として更に親から受け取ってるかもしれない )。
個人的には、それは辛いと思うほうがおかしいと言うか、props の責務がまともになってさえいれば階層が4〜5段になるぐらい大したことではない(それによってテストしやすくなったりするのだし)と返すべきなんだと思う。
Context は横断的な関心事(めっちゃいろんなコンポーネントで使うとか)をメインの動機にすべきで、「階層が深いから」は本質的ではないと思う。
なんとなくネストが深いと本能的に不安になる、みたいな美意識は(特に React 初心者には)根強くあるらしい。感覚的にわからなくはないが、そこは慣れてくださいと言ったほうが良い気がする。
I think we have about 30 at the root of the new FB website. So what?