Reactにおける高階コンポーネントを例とともに理解する

高階コンポーネント(HOC)はReactのパターンの一つで、最初は混乱するかもしれませんが、理解すれば、コンポーネントロジックを抽象化して再利用するための価値あるツールになります。このガイドでは、HOCとは何か、その使い方、そしていつ使うべきかを明確な例とともに学びます。
重要なポイント
- Reactにおける高階コンポーネントとは何かを理解する
- 明確なコード例でHOCの書き方と使い方を学ぶ
- HOCを使うべき時と使うべきでない時を知る
高階コンポーネントとは何か?
高階コンポーネントは、コンポーネントを受け取り、新しいコンポーネントを返す関数です。元のコンポーネントを直接変更することなく、振る舞い、プロップス、またはロジックを追加します。
基本構造
const withExtraLogic = (Component) => {
return function WrappedComponent(props) {
// ここにカスタム動作を追加
return <Component {...props} />;
};
};
これはデコレーターのようなものと考えてください:何かをラップして強化してから渡します。
なぜHOCを使うのか?
- コードの再利用:繰り返しのロジックを抽象化
- 横断的関心事:ログ記録、認証、トラッキング
- プロップの注入:コンポーネント内部を複雑にせずにロジックに基づいてプロップを注入
基本例:withLogger
const withLogger = (Component) => {
return function WrappedComponent(props) {
useEffect(() => {
console.log('マウントされました:', Component.name);
}, []);
return <Component {...props} />;
};
};
const Hello = () => <h1>Hello</h1>;
const LoggedHello = withLogger(Hello);
// 使用法
<LoggedHello />
これは、元のHello
コンポーネントに触れることなく、コンポーネントがマウントされたときにその名前をログに記録します。
もう一つのユースケース:withWindowWidth
このHOCは、ウィンドウの幅をプロップとして任意のコンポーネントに追加します:
const withWindowWidth = (Component) => {
return function WrappedComponent(props) {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return <Component {...props} windowWidth={width} />;
};
};
const DisplayWidth = ({ windowWidth }) => <p>幅: {windowWidth}px</p>;
const ResponsiveDisplay = withWindowWidth(DisplayWidth);
これにより、リサイズロジックを複製することなくレスポンシブなコンポーネントを作成できます。
HOC vs フック vs レンダープロップス
パターン 説明 使用するとき… HOC 新しいコンポーネントを返す関数 多くのコンポーネントに動作を追加したい場合 フック use*
を使用する再利用可能な関数 再利用可能なステート付きロジックが必要な場合 レンダープロップス 関数を子として渡す 関数を通じて動的なレンダリングが必要な場合
フックとの詳細比較
今日ではロジック共有にはフックが推奨されていますが、違いがあります:
- HOCは外部ラッパーです。フックはコンポーネント内部で実行されます。
- フックは合成性を提供します — 1つのコンポーネントで複数のフックを簡単に呼び出せます。
- フックはrefとローカルステートと自然に連携しますが、HOCは
ref
の転送に特別な配慮が必要です。
同じロジック、異なる実装
withWindowWidth
の代わりにフックを使用する例:
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
const DisplayWidth = () => {
const width = useWindowWidth();
return <p>幅: {width}px</p>;
};
このバージョンは余分なラッパーを避け、現代のReactではより読みやすくなっています。
HOCの制限
- ラッパーのネスト:多すぎるHOCはコンポーネントツリーのデバッグを難しくする
- 命名:匿名ラッパーコンポーネントはDevToolsでの読み取りを難しくする
- Refの転送:
forwardRef
を使用しない限り、標準のrefは機能しない
ref問題の例
const withWrapper = (Component) => (props) => <Component {...props} />;
const FancyInput = forwardRef((props, ref) => <input ref={ref} {...props} />);
forwardRef
がなければ、ref
は実際の入力ではなくラッパーを指すことになります。
ベストプラクティス
- 常にプロップスを渡す:
<Component {...props} />
- デバッグのためにラップされたコンポーネントに名前を付ける:
WrappedComponent.displayName = `withLogger(${Component.displayName || Component.name})`;
- フックがより明確に仕事をこなせる場合はHOCを避ける
結論
高階コンポーネントは、コンポーネントロジックを拡張して再利用するための柔軟な方法です。現代のReactではフックの方が一般的ですが、HOCにはまだ役割があります — 特に既存のコンポーネントの内部を変更せずにロジックを追加したい場合に有用です。コードをより複雑にするのではなく、より明確にする場合に使用しましょう。
よくある質問
はい、現在はあまり一般的ではありませんが。フックが多くのユースケースに取って代わりましたが、HOCは複数のコンポーネントに動作を注入するのに依然として有用です。
HOCはコンポーネントをラップして新しいコンポーネントを返します。フックは内部からコンポーネントにロジックを追加する関数です。
`forwardRef`を使用してrefを転送する場合のみ可能です。そうでなければ、refは元のコンポーネントではなくラッパーを指します。
認証やログ記録など、同じ外部ロジックで多くのコンポーネントをラップしたい場合で、そのロジックを繰り返し書き直したくない時にHOCを使用します。