Back

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

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を使用します。

Listen to your bugs 🧘, with OpenReplay

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