こんにちわ!本日はReactで複数のコールバックをコンポーネント間で綺麗に受け渡しする方法をご紹介します。
propsによるコールバック渡しは安直すぎる
通常あるコンポーネントから子孫コンポーネントへ値を渡すにはpropsを使うのが定石ですが分かりやすい反面、途中で経由されるコンポーネントは無駄にコードの可読性が下がり何のうまみも得られません。俗にいうProp Drilling(バケツリレー地獄)です。
特にTypeScriptなどで書いているとコールバックの引数や戻り値が変わる度に経由するコンポーネントを含めて広い範囲でコードの修正が必要となるのでできれば回避したいというのが私の意見です。
useContext()を使ってみる
React HooksのひとつuseContext()を使えば、あるコンポーネントから配下のコンポーネントへcontextとして値を渡すことができます。値を利用するコンポーネントでは明示的にuseContext()でサブスクライブする必要があるため無意味なProp Drillingが無くなりスッキリします。
利用時の注意点としては、下記の2点が挙げられます。
(1) プロバイドする値を変更すると全てのサブスクライバーが再レンダリングされる
(2) プロバイダーにはプリミティブな値 or メモ化されたオブジェクトを設定する
(2)の制約はcreateContext()するコンポーネント自身が再レンダリングされる際に、valueにメモ化されていないオブジェクトが当たっていると厳密等価演算でfalseとして扱われて(1)がトリガーされるというレンダリング地獄を防ぐためです。このためプロバイダーに渡す値やSetterにはuseState()を使うことが多いと思います。
const messageContext = createContext("default")
function TopComponent() {
const [data, setData] = useState("Hello World!");
return (
<messageContext.Provider value={data}>
<SomeComponent />
</messageContext.Provider>
);
}
値とSetterのプロバイドという単純な構造であればuseState()で十分ですが、上位コンポーネントだけが持つオブジェクトに対してさまざまな処理を行うコールバック群をプロバイドしたい場合には少し考える必要があります。
コールバック群をメモ化する
ようやく本題ですが、複数のコールバックからなるオブジェクトをuseContext()でプロバイドするにはどうすれば良いでしょうか。コールバックのメモ化なので当然useCallback()が必要です。さらにそのオブジェクトなのでuseMemo()を併用すれば良いということになります。
const cbContext = createContext({
callbackA: undefined,
callbackB: undefined,
})
function TopComponent() {
const callbackA = useCallback(() => {// 何らかの処理}, [])
const callbackB = useCallback(() => {// 何らかの処理}, [])
const callbacks = useMemo(() => ({
callbackA,
callbackB,
}),[])
return (
<cbContext.Provider value={callbacks}>
<SomeComponent />
</cbContext.Provider>
);
}
まとめ
今回は久々のReact関連の記事でした。Propsは理解が容易い概念ではありますが、可読性も考えてuseContext()や他の最適な手段に置き換えれないか追求し続けることが重要そうですね。
この記事が面白かった方、参考になった方は、是非「イイね」お願いします。