HOCとRender PropsとHooksの使い分け
前提
HOC
React のコンポジションの性質から生まれる設計パターン。
横断的関心事を処理。
ラップされたコンポーネントはコンテナの props のすべてに加えて新規のプロパティである data を受け取り、出力の描画に使用します。外側にある HOC は渡すデータが使われる方法や理由には関心がありませんし、ラップされたコンポーネントの側はデータがどこからやって来たのかには関心を持ちません
規則
- 自身に関係のない props はラップされるコンポーネントにそのまま渡す
- 組み立てやすさを最大限保つ
- デバッグしやすくするため表示名をラップする
Render Props
あるコンポーネントが何をレンダーすべきかを知るために使う関数型の props。
横断的関心事を処理。
多くの高階コンポーネント (HOC) がレンダープロップを使った通常のコンポーネントによって実装可能。
Hooks
フックを使えば、ステートを持ったロジックを、コンポーネントの階層構造を変えることなしに再利用できる
React をしばらく使った事があれば、この問題を解決するためのレンダープロップや高階コンポーネントといったパターンをご存じかもしれません。しかしこれらのパターンを使おうとするとコンポーネントの再構成が必要であり、面倒なうえにコードを追うのが難しくなります。典型的な React アプリを React DevTools で見てみると、おそらくプロバイダやらコンシューマやら高階コンポーネントやらレンダープロップやら、その他諸々の抽象化が多層に積み重なった『ラッパー地獄』を見ることになるでしょう
それぞれ得意なことは何なんだろう。
HooksはRender PropsやHOCなどによるラッパー地獄を回避するための代物らしいが、、🤔
参考記事1
Comparison: HOCs vs Render Props vs Hooks
概要
- 以下の観点から総合的に判断してrender propsが一押し。僅差でhooks。hocは使わないほうが良い。
- 可読性
- 再利用のしやすさ
- カスタムのしやすさ
- デバッグのしやすさ
- 単体テストのしやすさ
- パフォーマンス
- いつHOCを使うか
- かなり抽象化してしまうのでほとんど使わないが、ごくシンプルな機能を既存のコンポーネントかサードパーティ製のコンポーネントに適用する場合には使っても良いかも
- 一部のライブラリには組み込みでHOCが提供されている
- Styled ComponentsのwithTheme
- Reduxのconnect
- https://react-redux.js.org/api/connect
- ただしRedux公式では、より簡潔で使いやすいとして、同機能を提供するhooksの方の使用を奨励している
- 一部のライブラリには組み込みでHOCが提供されている
- できる人間だと思われたくて同僚に嫌われたいなら使えばいいと思う(皮肉)
- かなり抽象化してしまうのでほとんど使わないが、ごくシンプルな機能を既存のコンポーネントかサードパーティ製のコンポーネントに適用する場合には使っても良いかも
- いつRender Propsを使うか
- レンダリングを完全に制御したいとき
- レンダリングとロジックを簡単に分離したいとき
- 読みやすいコードを書きたいとき
- クラスコンポーネントを書きたいとき
- いつHooksを使うか
- レンダリングとロジックを分離したいとき
- Render Propsのネストをたくさんする冗長な書き方を避けたいとき
- 読みやすく再利用しやすいコードを書きたいとき
- 3つはメリデメがあるので、組み合わせて使うのがベスト
参考記事2
React: Hooks vs. Render Props vs. Higher-Order Components
概要
- HOC, Render Props, Hooksのどれかを使うか決める際は、可能ならどんな状況でもHooksを選ぶべき
- HOCの何がだめなのか
- propsの名前がリネームできないので、他で定義されている同名のpropsを上書きする恐れがある
// HOC
<MyComponent x="some value" y="some other value" />
// ^をHOCでラップする際にwithPageにxまたはyが含まれていたらMyComponentのxまたはyが上書きされる
export default withPage(MyComponent)
- HOCにpropsで何が渡されるかというのが呼び出し元からだとわからず、可読性に欠ける
- Render Propsの何がだめなのか
- Render Propsのreturn内でしかデータが使えない
<Mouse>
{({ x, y }) => (
<Page>
{({ x: pageX, y: pageY }) => {
// ^ big brain
}}
</Page>
)}
</Mouse>
- 容易にネストするので可読性が落ちてしまう
// 3つRender Propsを使うだけでめちゃネストする
const MyComponent = () => {
return (
<Mouse>
{({ x, y }) => (
<Page>
{({ x: pageX, y: pageY }) => (
<Connection>
{({ api }) => {
// yikes
}}
</Connection>
)}
</Page>
)}
</Mouse>
)
};
- Hooksの何がいいか
- propsの名前をリネームできる
const { x, y } = useMouse();
const { x: pageX, y: pageY } = usePage();
- 何がpropsとして渡されるかがわかりやすい
- return外でもデータが使える
const { x: pageX, y: pageY } = usePage();
useEffect(() => {
// this runs whenever pageX or pageY changes
}, [pageX, pageY]);
- 全然ネストしない
const { x, y } = useMouse();
const { x: pageX, y: pageY } = usePage();
const { api } = useConnection();
参考記事3
The Current State of React HoCs, Hooks, and Render Props
概要
- HOC, Render Props, Hooksにはそれぞれ使い所があり、それぞれの完全な代替にはなりえない。Hooksがメジャーになった今でも、HOCやRender Propsを使ったほうが良いケースはいくつかある
- 例えばテストのしやすさで言えばHOCに分がある
- Reduxのconnect()を例に考える
// MyComponentをラップしたHOCを返す
connect()(MyComponent);
- ReduxではuseSelectorとuseDispatchを使えば、connect()を使わなくても同じことを実現できる
- が、HOCは全てのdataをprops経由で受け取るのでテストはしやすい
参考記事4
Thoughts on React Hooks, Redux, and Separation of Concerns
概要
- Hooksに飛びついて銀の弾丸の如く扱うのは間違い
- HooksにもHOCにもトレードオフがあるのでそれを認識して取り扱っていくべき
- HOC
- メリット
- すべてのデータをprops経由で受け取るのでシンプルなコンポーネントになりやすい
- 関心の分離がしやすい
- デメリット
- props名の衝突
- componentのネスト
- 静的型付けが複雑になる
- refでのアクセスができない
- https://ja.reactjs.org/docs/higher-order-components.html#refs-arent-passed-through
- 今はforwardRefを使えば解決できるみたい
- メリット
- Hooks
- メリット
- componentのネストを抑えられる
- シンプルな関数としてロジックを切り出しやすい
- 静的型付けがしやすい
- 構成しやすい
- デメリット
- 関心の分離は実現しにくくなってしまう
- props経由以外でもデータを受け取れるし、stateを定義したりできるので、扱う関心事が複数になりやすい
- 関心の分離は実現しにくくなってしまう
- メリット
- HOC
参考記事5
概要
- Render Propsは死んでない = Hooksが出てきた今でも有用性はある
- Hooksを使うと再レンダリングが容易に発生しパフォーマンスが低下するリスクがある
- 以下の例だとinputに入力されるたびにHeaderやNavigationなどの子コンポーネントも合わせて再レンダリングされてしまう
function Page() {
// react-hook-formとは関係ないcustom hook
const { values, setValue } = useForm();
return (
<>
<Header />
<Navigation />
<SomeOtherThirdPartyComponent />
<form>
<input
value={values.name}
onChange={e => {
setValue("name", e.target.value);
}}
/>
<input
value={values.email}
onChange={e => {
setValue("email", e.target.value);
}}
/>
</form>
<Footer />
</>
);
}
- Render Propsを使えば特定の箇所だけ再レンダリングすることが可能
- 以下の例だとFormManagerコンポーネント内だけ再レンダリングされる
function Page() {
return (
<>
<Header />
<Navigation />
<SomeOtherThirdPartyComponent />
<FormManager>
{({ values, setValue }) => (
<form>
<input
value={values.name}
onChange={e => {
setValue("name", e.target.value);
}}
/>
<input
value={values.email}
onChange={e => {
setValue("email", e.target.value);
}}
/>
</form>
)}
</FormManager>
<Footer />
</>
);
}
- 再レンダリングをより制御したい場合はRender Propsを使うべき
所感
- 基本的にHooksでよさそう
- 以下のデメリットも把握しておけば対処できそう
- 関心の分離がしづらい、内容が肥大化しがち
- 適宜componentを切り分ける
- 再レンダリングを制御しきれない
- ユースケースに応じてreact-hook-formとかで非制御コンポーネントを活用
- 関心の分離がしづらい、内容が肥大化しがち
- 以下のデメリットも把握しておけば対処できそう
- HOCについてはprops管理がしづらかったりして導入するメリットはそこまでないが以下の全てに当てはまる場合は使ってもいいかもしれない
- ログ出力などの横断的関心事を扱う場合
- HOCのネストは避ける
- チーム開発の場合は、携わるメンバーがある程度HOCの知識・理解がある
- Render Propsは再レンダリングを完全にコントロールしたい & 非制御コンポーネントは使いたくない場合に使うと良いかも