React Contextの使用を控える
背景
- jotaiやcontextの使用はあまり好ましくないというのがおそらくReactコミュニティのスタンダード
- しかし、「なぜ好ましくないのか」が普及してないイメージがあるので再度まとめ直すことでしっかりと根拠を持ってContextの使用・不使用を判断できるようにする
前提:あらゆる実装でContextは使用できる
前提としてコンテキストを使用したい、もしくは使用できる箇所を押さえます。
結論を言うと、あらゆる実装でコンテキストは使用できます。
「親が状態を作り、子が状態を使う」など複数のコンポーネント間でステートのやり取りをするのであれば、その状態のやり取りをpropsではなくcontextに任せることが可能です。
例) propsでの実装をcontextに置き換える
// propsを使った実装
const Parent = ()=>{
const [count,setCount] = useState(0)
return <div>
<Child count={count} />
<button onClick={()=>setCount(count+1)}> Count up </button>
</div>
}
const Child = ({count})=>{
return <div>{count}</div>
}
// contextを使った実装
const context = createContext(0)
const Parent = ()=>{
const [count,setCount] = useState(0)
return <context.Provider value={count}>
<div>
<Child />
<button onClick={()=>setCount(count+1)}> Count up </button>
</div>
</context.Provider>
}
const Child = ()=>{
const count = useContext(context)
return <div>{count}</div>
}
あらゆる箇所で使用できるのであれば、あらゆる箇所で使用すべきでしょうか?
当然そうではないです。
一旦こういう基本的なところからcontextの用途について押さえていこうと思います。
Contextは多くの場面では使うべきではない
前述の例を考えると、contextではなくpropsを使うべきであることはおそらく多くの人が納得すると思います。
ただpropsを使うべきであると直感的に自明すぎて、それが本当に正しいのかすら考えたこともないと思うので、一旦propsを使うべき理由を言語化します。
propsを使うべき理由を言語化するとしたら次になります。
- コード量が減るから
- propsは直接渡されるのでcontextを使った実装と比較すると挙動が明らかになるから
ここでそれぞれの理由についてもう少し考えると、
1の理由はあくまで今回の例がそうだっただけで、他の実装でもそうとは限らない
2の理由はpropsとcontextの比較をするときに常に付きまとうものである
上記に加えて、実はpropsもあらゆる実装で使用できるものであることを踏まえると
「2の理由を超えて優先されるべき理由が見つからない限り、あらゆる実装でpropsを使うべきである」ということがわかります。
つまりは多くの場合contextを使うべきではないです。
Contextを使っていい箇所
ではContextはどこでは使っていいのでしょうか。
色々考えられるでしょうが、代表的なものだけ説明します。
コンテキストを使わないと実装が困難な箇所
たとえば、Reactの<ThemeProvider>
や<Router>
、あるいはi18nの<IntlProvider>
など、Reactの全体構造に関わるような「トップレベルで値を供給し、ツリー全体で参照したいがpropsでの伝搬が現実的でない」ケースが該当します。
これらは共通して、
- コンポーネント階層の深いところからアクセスされる
- 値が基本的に変わらない(=レンダリングコストが無視できる)
- propsで明示的に渡すと現実的でないほど冗長になる
といった特徴を持っており、Contextが唯一の合理的な選択肢になります。
どこからでもアクセスできるデータソース(localStorageやAPIなど)にアクセスする箇所
たとえば、「認証情報をlocalStorageから取得する」「ユーザー設定をAPI経由で取得する」といったケースでは、そもそもデータのスコープがコンポーネントツリーとは独立しており、UI側の構造と結びつける合理的な手段がContextしかないという状況もあります。
このような「グローバルなデータソース」へのアクセスに関しては、Contextは「共通のデータ取得・キャッシュの仕組みを注入する」という目的で使われるため、役割的にも適しています。特に、APIクライアントや認証トークンの注入などは、アプリケーション全体に影響する外部依存性の注入ポイントとしてのContextの使い方と相性がよく、実際に多くのライブラリでもこの方式が採用されています。
ただしこの場合も、実際の状態の管理は別のステート管理ライブラリ(例:jotaiやSWR、React Queryなど)に委ねるべきであり、Contextはあくまで「アクセス可能にする手段」に限定すべきです。
Contextを使ってはいけない箇所
ほとんどの箇所でContextを使ってはいけないのですが、Contextを使いたくなるシチュエーションの代表的なものだけ挙げて、なぜ使ってはいけないのか簡単に説明します。
バケツリレーが多い箇所
「propsのバケツリレーが面倒だからContextで解決しよう」というのは、Reactでよくある誤解です。確かに、3階層・5階層と渡すのが手間に感じると、Contextで一発で解決したくなります。
しかし、propsを渡し続けるのが手間に感じるのは、そもそも状態のスコープが間違っているか、責務の分離ができていないからです。バケツリレーが多いのは「Contextが必要だから」ではなく、「ロジックが分離されていないから」起きている現象にすぎません。
Contextで「解決したつもり」になると、ツリー全体の責務が曖昧になり、コンポーネントがグローバルな依存に気づかずに動くようになってしまいます。これは可読性・再利用性・テスト性すべての観点で有害です。
コンポーネントの責務の分離については、Honey 32さんの https://qiita.com/honey32/items/b9f70f960e891f031b0f がわかりやすいと思います。
実装を隠蔽したい箇所
「中でどう動いているかを隠したい」「詳細を意識せず使わせたい」というような考えでContextを使いたくなる場面もあります。しかし、その目的にもContextは向いていません。
Contextは「値を注入する仕組み」であり、「ロジックの隠蔽や抽象化」を行うためのツールではないからです。隠蔽を目的とするなら、Custom HookやProviderコンポーネントの中に内部実装を閉じ込めるのが正解です。これにより、外からは状態管理の仕組みを意識せず使え、かつ責務も明確に分離されます。
Contextで隠そうとすると、外部からアクセス可能な状態が過剰に広がり、必要以上に密結合な設計になってしまうことが多く、将来的な変更耐性も落ちます。
結論:Contextは「最終手段」くらいでちょうどいい
ReactのContextは、使い所を誤ると保守性に悪影響を及ぼします。ただ、正しく使えば、コードをシンプルに保ち、アプリ全体の設計をきれいに保つ手助けをしてくれます。
特に「Contextが本当に必要な場面は限られている」という前提を持つことで、むしろ他の状態管理手法(props、hooks、外部ライブラリ)との使い分けがしやすくなり、設計に一貫性が生まれます。
「本当にContextがふさわしい場面だけに使う」という意識を持ってあげれば、Contextは優秀な味方になってくれるでしょう。
Discussion