Contextをおさらいする
React では、コンポーネント間の状態は props として親コンポーネントから子コンポーネントへ渡す必要がありますが、複数の親子関係にいないコンポーネントを跨いで共通で利用したい値、例えばロケールの情報や UI のテーマ、ログイン状態など、がある場合、Context を使うことで面倒なバケツリレーをすることなくグローバルな値としてコンポーネント間で情報を共有できます。
Context の使い方
Context を利用するときは Context Provider で値を受け取り、その配下のコンポーネント内で変更を購読することができるようになります。
React には useContext という フックが用意されており、Context Provider 配下のコンポーネントからuseContext(<コンテキスト名>)
でコンテキストの値を購読することができます。
import React, { createContext, useState } from 'react' import { MembersCard } from './components/MembersCard' // CustomThemeContextというContextを作成 export const CustomThemeContext = createContext<{ setTheme: Dispatch<SetStateAction<string>> }>({ setTheme: () => {} }) function App() { const [theme, setTheme] = useState<string>('light') return ( <CustomThemeContext.Provider value={{ setTheme }}> <div className="App"> <MembersCard /> {/** <-CustomThemeContextの値を講読できるよ */} </div> </CustomThemeContext.Provider> ) } export default App
useContext を呼び出すコンポーネントはコンテキストが更新されるたびに再レンダーされるので、闇雲に利用するとコンポーネントの再利用性が落ちるので注意が必要です。
闇雲に利用してあまりよくなかったケース
実際の案件で以下のような要件がありました。
- Global メニューの開閉状態を常に保持したい
- ページ遷移をした後も遷移前のページの UI 状態を維持したい
全ページ共通のグローバルメニューの開閉状態がページ遷移の度にリセットされてしまうので、Context に持たせることで状態の維持に対応しました。
しかし、これだと この Context を参照している他のコンポーネント、つまりグローバルメニューを表示しているページ全部が Context の値が更新されるたびに再レンダーされてしまいます。
議論の末、上記の理由から UI の状態を Context に持つのは良くないよね、という結論に至りました。
Context を使った方がいい時
Context や状態管理ライブラリを使わない場合、コンポーネント間のデータの連携には props のバケツリレーが必須になります。
バケツリレーをするにはコンポーネント同士が親子関係にある必要がありますが、props で値を受け取りたいためだけに親子関係を作ることはコンポーネント同士の密結合に繋がるため、コンポーネントの再利用性が低下します。同じように親子関係を構築し、必要な値を props で渡してあげる必要があるからです。
このようなケースには Context もしくは Redux 等の状態管理ライブラリを利用するのが良いと考えます。
Context を使わない方がいい時
先ほどの Global メニューの状態を Context で管理した失敗例がわかりやすいですが、Context を更新したときに再レンダーされるべきでないコンポーネントにも再レンダーが走る場合は Context で管理すべきではないと言えます。
公式ドキュメントには Context を使った方が良いのは"何らかのデータが、ネストレベルの異なる多くのコンポーネントからアクセスできる必要がある時"と明記されており、このケースに当てはまるのは Theme の設定やロケールの情報などごくわずかだと考えます。