12k
All articles

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

ReactのHOC(高階コンポーネント)は既存コンポーネントをラップし、propsの注入とロジック共有を実現する。hooksやref転送との比較もコード例を交えて解説する。

OpenReplay Team
OpenReplay Team
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にはまだ役割があります — 特に既存のコンポーネントの内部を変更せずにロジックを追加したい場合に有用です。コードをより複雑にするのではなく、より明確にする場合に使用しましょう。

よくある質問

高階コンポーネントはReactでまだ使用されていますか?

はい、現在はあまり一般的ではありませんが。フックが多くのユースケースに取って代わりましたが、HOCは複数のコンポーネントに動作を注入するのに依然として有用です。

高階コンポーネントとフックの違いは何ですか?

HOCはコンポーネントをラップして新しいコンポーネントを返します。フックは内部からコンポーネントにロジックを追加する関数です。

HOCでrefを使用できますか?

`forwardRef`を使用してrefを転送する場合のみ可能です。そうでなければ、refは元のコンポーネントではなくラッパーを指します。

フックの代わりにHOCを使うべき時はいつですか?

認証やログ記録など、同じ外部ロジックで多くのコンポーネントをラップしたい場合で、そのロジックを繰り返し書き直したくない時にHOCを使用します。

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers

We use cookies to improve your experience. By using our site, you accept cookies.