🐡

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を使うべき理由を言語化するとしたら次になります。

  1. コード量が減るから
  2. 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