Reactのコールバックパターン: Propsで関数を渡すときに、プロが意識している「境界線」の話
Reactの世界にどっぷり浸かっていると、「子コンポーネントにイベントハンドラを渡す」なんてのは呼吸をするのと同じくらい当たり前の作業になりますよね。でも、中級者から一歩先へ進むには、単に「動くコードを書く」だけでなく、「なぜその設計にするのか」「ブラウザはどう解釈しているのか」という深淵を理解する必要があります。
今回は、Propsとして関数を渡すパターンの「正解」と、実務でトラブルを未然に防ぐための設計術を、現場の視点から叩き込みます。
—
1. なぜ「Propsとしての関数」が重要なのか?
Reactのコンポーネントは本来、単なる「表示用の関数」です。しかし、UIはインタラクティブであってこそ。親が握っている「状態(State)」や「ビジネスロジック」を、子が直接いじくり回すのはご法度です。そこで登場するのが、コールバックパターンです。
親が定義した関数をPropsとして子に渡し、子がそれを「トリガー」として実行する。これは単なるデータ渡しではなく、「責務の分離(Separation of Concerns)」そのものなんです。
2. TypeScriptでの型付け: `void` と `unknown` の使い分け
現場で一番よく見るのが、安易に `any` を使ってしまうパターンです。これをやると、後々地獄を見ます。まずは、以下の型定義を見てください。
// 悪い例:関数の型が不明瞭で、将来的な破壊的変更に弱い
type Props = {
onClick: any;
};
// 良い例:シグネチャを明確にする
// 引数を受け取らない場合は () => void
// イベントオブジェクトを渡す場合は (e: React.MouseEvent
type Props = {
onAction: (id: string) => void;
};
シニアからの教え:
戻り値を `void` にするのは「この関数は実行されるだけ(副作用を起こすだけ)で、結果を親に返さない」という強力なメッセージです。もし親に値を返したいなら、それは「コールバック」ではなく「データフェッチ」か「ステートの同期」の設計を見直すべきタイミングかもしれません。
—
3. 実践:クリーンなコールバック実装
以下は、実務でも通用するコンポーネントの構造です。特に注目してほしいのは、`useCallback` の使いどころです。
import React, { useCallback } from ‘react’;
// 子コンポーネント
// React.memoでラップして、無駄な再レンダリングを防ぐのが「プロの嗜み」
const ActionButton = React.memo(({ label, onClick }: { label: string; onClick: () => void }) => {
console.log(`${label} が描画されました`);
return ;
});
// 親コンポーネント
export const ParentComponent = () => {
// useCallbackでメモ化しないと、Parentがレンダリングされるたびに
// 新しい関数インスタンスが生成され、子コンポーネントのReact.memoが無効化される
const handleClick = useCallback(() => {
console.log(‘親のロジックが実行されました!’);
}, []); // 依存配列が空なら、この関数は一生同じインスタンスを保つ
return (
);
};
—
4. 裏側で何が起きているのか?(ブラウザの視点)
ここで一度立ち止まって考えてみましょう。「関数を渡す」とはどういうことか。
JavaScriptにおいて、関数は「第一級オブジェクト」です。Reactがコンポーネントを再レンダリングするたびに、関数もメモリ上に新しく再定義されます。
もし、子が `React.memo` を使って最適化していても、親が渡す関数が毎回「別のインスタンス」であれば、Reactは「Propsが変わった!」と判断して子を再レンダリングします。
現場の泥臭い事実:
「React.memoを使っているのにパフォーマンスが改善しない」という相談の9割は、この関数インスタンスの生成問題です。`useCallback` は単なるおまじないではなく、ブラウザのメモリ効率とレンダリングの最適化を制御するための、フロントエンドエンジニアの「武器」なのです。
—
5. まとめ:明日からの設計基準
実務でコードを書く際は、以下の3点を意識してください。
1. 関数の型は「何を受け取り、何を返すか」を厳密に書く。 `any` はチームの未来を奪います。
2. 子コンポーネントを `React.memo` で包むなら、渡す関数は必ず `useCallback` で囲む。 これがセットです。
3. 命名を疎かにしない。 `onClick` や `onChange` も良いですが、`onDeleteUser` や `onFetchData` のように、コンポーネントが「何をするための関数か」をProps名で伝えてください。
Reactは自由度が高い分、書き手の意識がコードの質に直結します。今回紹介したパターンは、単なるテクニックではなく、「コンポーネント間の疎結合を保つための作法」です。
コードは誰かが読み、そして将来の自分が修正します。その時に「ああ、この人は何をしたいのか」が直感的に伝わるコードこそ、最高峰のフロントエンドコードと言えるでしょう。自信を持って、クリーンなコンポーネントを書いていきましょう!

コメント