Reactでコンポーネント間のデータをやり取りする際、propsを深くネストして渡す「propsドリリング」はコードの可読性を低下させ、保守を困難にします。useContextを使うことで、どのコンポーネントからでも直接値を取得でき、propsを逐一渡す手間を大幅に減らせます。この記事では、useContextの基本的な使い方から応用テクニック、パフォーマンス対策まで最新情報をふまえて体系的に解説します。React初心者から中級者まで幅広く役立つ内容です。
目次
React useContext 使い方の基本概要
ReactでuseContextを使うときの基本は、まずContextを作成し、Providerで値を提供し、consumer側でuseContextでその値を取得する流れです。propsドリリングを避けたい、テーマやログイン状態などアプリケーションの中で複数のコンポーネントから共有される情報がある場合に非常に有効です。ReactのバージョンはHooks対応以降が対象となります。
この構造を正しく理解することで、Reactアプリのコードがシンプルで拡張しやすくなります。
createContextとProviderの役割
まずReact.createContextを使ってContextを定義します。この時、デフォルト値を設定でき、Providerを使ってそのContextに値を渡します。ProviderはContextの値を持ち、その値は子孫のどのコンポーネントからでも取得可能です。Providerのvalueプロパティに渡す値が更新されると、useContextを使っている全ての子孫が再レンダリングされます。これは、Contextの値がオブジェクトの場合、同じ内容でも参照が変わると変化とみなされる点に注意が必要です。
また、Providerはコンポーネントツリーのかなり上の方で定義しなければ、期待通りに値が渡らないことがあります。
useContextで値を取得する方法
子孫コンポーネントでuseContext(SomeContext)を呼び出すことで、最も近くのProviderから渡されたvalueを取得できます。もしProviderが存在しなければ、createContextで設定したデフォルト値が返ります。関数コンポーネント内でのみ使えるHookであり、呼び出すタイミング(トップレベル)にも制約があります。
この取得操作はリアクティブで、Contextのvalueが変更されるとそのコンポーネントが自動的に再レンダリングされます。ただし、必要以上の再レンダリングを避けるため、使用する値の構造や変更頻度を考えた設計が重要です。
propsドリリングとは何か
propsドリリングは、あるデータを子孫の深い階層まで渡したいときに、途中のコンポーネントがそのデータを受け取って渡すだけの役割を持つ現象です。見た目上は簡単な構造でも、階層が増えるとpropsの受け渡しが鬱陶しくなるうえ、変更時の影響範囲が大きくなります。useContextを使えば、このような中間コンポーネントを介さずに必要なコンポーネントで直接値を取得できるようになり、コードの見通しが良くなります。状態管理やテーマ・認証情報など頻繁に使われる値にはContext+useContextの組み合わせが特に有効です。
実際のコードで学ぶReact useContext 使い方ステップ
以下では具体的なコード例を順を追って示しながら、Contextの作成からProviderでの値設定、子コンポーネントでの利用、propsドリリングからの解放までの流れを追います。最新のReactの仕様を前提としており、新しいReactプロジェクトでもそのまま使える方法です。ステップ毎に注意点やベストプラクティスも紹介します。
ステップ1:Contextの作成
まず、必要なContextを作成します。これは共有したいデータや機能に応じて複数作ることができます。名前付けはDescriptiveに行い、PascalCaseでContext名を決めるとわかりやすいです。デフォルト値は型に応じて設定するかnullを使います。例えばログイン中のユーザ情報やテーマ設定など、初期状態を明示しておくことで予期せぬundefinedエラーを防げます。
ステップ2:Providerで値を渡す
Appやmainのようなルートまたは必要とされる範囲が広い親コンポーネントでProviderを配置し、valueプロパティに共有する状態や関数を渡します。valueにはuseState、useReducer、useMemoなどを組み合わせて、値が頻繁に変わるものを効率的に扱う設計にすると良いです。頻繁に変化しない値は安定させて参照の長さを保つことで余分な再レンダリングを防げます。
ステップ3:useContextで値を受け取る
Consumerとして機能する子孫コンポーネントでuseContext(Context名)を呼び出します。取得した値をそのコンポーネントで使い、JSXでレンダリングします。値がnullでないかのチェックを入れる、型が期待通りかを確認するなど安全性を考慮します。もしContextが複数あれば複数のuseContextを使うか、カスタムHookを作成して抽象化します。
ステップ4:propsドリリングからのリファクタリング例
従来のやり方では、親→子→孫と深くプロップを渡す例をよく見ますが、これをContextに書き換えることで中間コンポーネントは値を受け渡す責務から解放されます。ContextのProviderを親側に置き、孫側など必要なコンポーネントでuseContextで取得するだけです。これにより、変更箇所が少なくなり可読性・保守性が向上します。
React useContext 使い方の応用テクニックとベストプラクティス
useContextを使う際には基本的な流れを押さえたうえで、アプリの規模や性能を考慮した応用テクニックを取り入れることでさらに効率的に使えます。ここでは複数Contextの設計、再レンダリングの抑制、カスタムHookとの併用、使うべきでない場面などについて解説します。
複数のContextを使って責務を分ける
テーマ、認証、言語設定といった異なる種類の情報を一つのContextに詰め込むと、ある値の変更で他の値を必要としないコンポーネントまで再レンダリングされてしまいます。情報の責務を分けて、特定のドメインごとにContextを分割することで再レンダリングの範囲を限定できます。これにより、無駄なパフォーマンスコストを抑制し、コードがよりモジュール化されます。
useMemo や useCallback を使ったパフォーマンス最適化
Contextのvalueにオブジェクトや関数を渡すと、valueが毎回新しい参照になるため、内容が同じでも再レンダリングが起こることがあります。これを防ぐためにuseMemoでオブジェクトをメモ化したり、useCallbackで関数を安定した参照にすることが有効です。パフォーマンス問題が見られる場合はProfilerなどで原因を特定し、最適化を行う設計が求められます。
カスタムHookでContext操作を抽象化
Contextの値取得や更新を行うロジックが複雑になる場合、useContextを呼び出すだけのカスタムHookを作ると良いです。これにより複数コンポーネントから使う処理を一箇所にまとめられ、再利用性やテストのしやすさが向上します。カスタムHookの名前は useXyzContext のようにわかりやすく命名し、Context側の責務を明確にしておくことが望ましいです。
useContextを使うべきでないケースとは
頻繁に変化する値や、高頻度で更新が発生するものをContextに入れると、依存するすべてのコンポーネントが再レンダリングされてしまい、性能が低下します。こうしたケースでは useState や useReducer、もしくは外部状態管理ライブラリを採用したほうが適切です。また、小規模な値や地域限定の状態であれば、propsで十分なこともあります。Contextの乱用は複雑化を招くため慎重に設計する必要があります。
React useContext 使い方の具体例とコード比較
ここでは簡単なテーマ切替と認証情報の共有を例に、propsドリリングあり・なしのコードを比較しながら説明します。最新のReact仕様に沿ったコード例で、理解を深めて実際の開発にすぐ応用できる内容です。
テーマ切替の例:propsドリリングありの場合
以下は3階層構造のコンポーネントで親からテーマを渡す例です。中間のコンポーネントがテーマをただ受け渡すだけの役割になってしまいます。コードの可読性が低く、変更時の影響範囲が大きくなります。
“`jsx 假定のコード例“`
“`jsx
function App(){
const [theme,setTheme]=useState(‘light’);
return(
);
}
function Page({theme,setTheme}){
return(
);
}
function Header({theme}){
return(
);
}
“`
テーマ切替の例:useContextを使った例
上記をContextを使って書き換えると中間コンポーネントを経由する必要がなくなります。Providerでテーマ状態を渡し、Headerコンポーネントで直接useContextで取得します。これがpropsドリリング回避の典型例です。
“`jsx 假定のコード例“`
“`jsx
const ThemeContext=createContext(null);
function App(){
const [theme,setTheme]=useState(‘light’);
return(
);
}
function Header(){
const {theme,setTheme}=useContext(ThemeContext);
return(
);
}
“`
認証情報の共有:複数Contextとの組み合わせ例
ログインユーザー情報とアプリの言語設定など、異なる責務の情報を別々のContextで管理すると、片方の変更が他方に不要な影響を及ぼすことを防げます。例えばAuthContextでユーザー情報を、LocaleContextで言語設定を管理し、それぞれProviderで包む構成です。コンポーネント内では必要なContextだけuseContextで取得すれば十分です。この分離設計により、変更が発生した場合の再レンダリングの範囲が狭くなり性能が向上します。
React useContext 使い方をマスターするためのチェックリスト
useContext利用時に迷ったり、パフォーマンス問題が生じたりすることを防ぐためのチェックポイントをまとめます。開発中にこのリストで確認すると、より質の高いコードが書けるようになります。
Contextを使うべき情報か検討する
その情報が複数のコンポーネントで共有されているか、頻繁に変更されるかどうかを見極めます。テーマや認証状態、言語などの頻度が低くてグローバルに使われる情報にはContextが向いています。逆に極端に頻繁に変わるステートや非常に狭い範囲でしか使わない情報には、ローカルなuseStateやpropsの方が効率的です。
再レンダリングの監視をする
Contextの値を変更したとき、消費するすべてのコンポーネントが再レンダリングされます。その挙動を把握するためにReact DevToolsのProfilerなどを使ってモニタリングすることが望ましいです。特にオブジェクトや関数をvalueに含める場合は、安定した参照を保つためのメモ化が必須です。
ContextのProviderを適切な位置に置く
Providerは必要な部分を包む最小範囲で定義します。全アプリを包む大きなProviderをひとつ設けると見た目は簡単ですが、変更時に大部分が影響を受ける可能性があります。逆にProviderが浅すぎると共有が必要なコンポーネントに値が届かない事態になりますので、責務範囲ごとにProviderを設計します。
値の安定性を確保する
頻繁に変わらない値に関してはuseMemoを使ってオブジェクトをメモ化し、関数はuseCallbackでラップして渡すことで、一見して内容が同じでも参照が変わることによる不必要な再レンダリングを防げます。このような最適化により、より大きなアプリケーションにおいてもパフォーマンスの低下を抑えられます。
React useContext 使い方に関するよくある質問(FAQ)
使っていく中で出る疑問を整理し、最新のReactでの挙動も含めて回答します。問題にぶつかったときのヒントとして役立ててください。
Contextの値がundefinedになる原因は何か
子コンポーネントがProviderの外にあったり、Providerにvalueプロパティが設定されていなかったりすることが一般的な原因です。また、異なるモジュールやバンドルによって同じContextオブジェクトが別物と認識されてしまう場合もあり、その際には比較演算子で等価であることが求められます。デフォルト値をnullまたは値を保証する型付きで設定しておくと安全です。
React 19のuse機能はuseContextに取って代わるか
React 19では新たな機能としてuseが議論されており、useContextと似た挙動を持つような機能が検討されていますが、現時点ではuseContextは引き続き標準的であり安全に使える方法です。将来的な仕様の変化に備えて、Contextの設計は柔軟にしておくことが望ましいです。
Contextで関数を渡すときの注意点は何か
関数をContextのvalueに渡すこと自体は問題ありませんが、毎回新しい関数インスタンスを作ってしまうと参照が変わり、useContextを使っているすべての子が再レンダリングされます。useCallbackで関数をラップし、依存性を適切に設定して安定した参照を保つべきです。また、関数が更新される度に状態や副作用が発生する可能性があるため、その設計も考慮します。
まとめ
Reactでpropsドリリングを回避し、共有状態を効率よく管理するためにuseContextは非常に有力な手段です。しかし、その使い方を誤ると逆にパフォーマンス問題や可読性の低下を招くこともあります。Contextの責務を明確に分け、Providerを適切な階層に置き、valueの安定性を確保する設計が重要です。
再レンダリングやvalueの形状に注意しながら、カスタムHookの併用や複数Contextの分割を意識すれば、Reactアプリの構造が整い、維持管理しやすくなります。以上の基本と応用を押さえれば、ReactにおいてuseContextの使い方をマスターしたと言えるでしょう。
コメント