HOCの「負の遺産」を終わらせる:関数合成によるクリーンなコンポーネント設計術
現場でReactを書いていると、必ず一度はぶつかる壁がある。「この共通処理、どのコンポーネントにも入れたいけど、毎回書くのはDRYじゃないよね?」という悩みだ。
かつて、この課題に対する銀の弾丸として持て囃されたのが HOC (Higher-Order Components) だ。しかし、クラスコンポーネント時代の遺物と思われがちなHOCも、適切に使えば今でも強力な武器になる。今日は、HOCの「本質」と、それを関数合成でエレガントに操る方法を、現場の視点から紐解いていこう。
—
HOCとは何か? – 「ラッパー」を超えた関数による抽象化
HOCは、決して「特別なReactの機能」ではない。ただの「関数」だ。
`Component => Component` という変換を行う高階関数に過ぎない。
ブラウザの裏側で何が起きているかと言えば、Reactがコンポーネントツリーを走査する際、HOCでラップされたコンポーネントは単なる「入れ子構造のノード」として評価される。つまり、HOCを使うほど、Reactの仮想DOMツリーには階層が深くなり、パフォーマンス上の微細なオーバーヘッドが蓄積する。
だからこそ、HOCは「慎重に、かつ最小限に」設計しなければならない。
実践:Propsを透かす「プロキシパターン」
HOCを作る際、最も重要なのは「元のコンポーネントに余計なノイズを与えないこと」だ。以下のコードは、権限チェックを付与するシンプルなHOCの例だ。
import React from ‘react’;
/
- 権限チェックを行うHOC
- @param {React.ComponentType} WrappedComponent – ラップ対象のコンポーネント
/
const withAuth = (WrappedComponent) => {
return (props) => {
const isAuthenticated = checkUserAuth(); // 認証状態の判定
if (!isAuthenticated) {
return
;
}
// propsをそのままSpreadすることで、コンポーネント間の通信を透過させる
return
};
};
ここで重要なのは、`{…props}` でPropsを漏らさず渡すこと。これがHOCの基本であり、プロキシ(代理)としての役割だ。
—
HOCの深淵:「HOCの地獄」をcomposeで解決する
実務でよくあるのが、`withAuth(withLogging(withRouter(MyComponent)))` のようなネスト地獄だ。これではコードの可読性が死ぬ。
ここで登場するのが `compose` 関数 だ。これは関数型プログラミングの知恵で、複数の関数を右から左へ合成する。
// 複数のHOCを合成するためのユーティリティ
// lodashのflowやreduxのcomposeと同じ考え方
const compose = (…funcs) => (comp) =>
funcs.reduceRight((wrapped, f) => f(wrapped), comp);
// 使用例
const EnhancedComponent = compose(
withAuth,
withLogging,
withTheme
)(MyComponent);
こうすることで、宣言的な記述が可能になる。`compose` を使うことで、どの順番でHOCが適用されるかが視覚的に明確になり、デバッグ時のスタックトレースも追いやすくなる。
—
シニアからの提言:HOCは「いつ」使うべきか?
今のReact界隈では、Hook (`use…`) の登場により、HOCの出番は確実に減っている。`useEffect` やカスタムフックで解決できるロジックを無理にHOCにする必要はない。
では、どんな時にHOCを採用すべきか? それは 「UIのレンダリング結果そのものを動的に変更(装飾)したい時」 だ。
- HOCが適している例:
- 特定の条件でコンポーネント全体をラップしてレイアウトを注入したい場合
- Propsのインタフェースを大きく書き換えるようなアダプターを作る場合
- 既存のサードパーティライブラリがHOCを前提としている場合
- Hookで解決すべき例:
- データのフェッチ、状態管理、イベントリスナーの登録(これらはカスタムフックに寄せるべき)
—
まとめ:現場で生き残るアーキテクチャのために
HOCは強力だが、使いすぎれば「コンポーネントのブラックボックス化」を招く。
デバッグする際、React DevToolsでツリーを開いた時に「`withAuth(withLogging(withTheme(MyComponent)))`」という名前ばかりが並んで、元のコンポーネントがどこにあるか分からなくなる……そんな現場を何度救ったか分からない。
もしHOCを使うなら、必ず `displayName` を設定してあげてほしい。
const withAuth = (WrappedComponent) => {
const ComponentWithAuth = (props) => { … };
// DevToolsでコンポーネント名が「WithAuth(MyComponent)」と表示されるようになる
const displayName = WrappedComponent.displayName || WrappedComponent.name || ‘Component’;
ComponentWithAuth.displayName = `WithAuth(${displayName})`;
return ComponentWithAuth;
};
HOCは、Reactという広大な海を渡るための「便利な道具」だ。道具に振り回されるのではなく、道具をどう配置すればチームのコードが読みやすく、保守しやすくなるか。その問いを常に持ち続けてほしい。
設計に絶対の正解はない。だが、少なくとも「なぜHOCを選んだのか?」を言語化できるエンジニアこそが、真に頼れるフロントエンドアーキテクトだと私は思う。

コメント