React

【React】useContextをいつ使う?別ファイルとデータ共有や再レンダリングについて

ReactのuseContextをいつ使うか、createContext・Provider・useContextの3ステップの使い方、別ファイルとのstate共有、再レンダリングの注意点をコード付きで解説します。

この記事の目次
  1. ReactのuseContextについて
  2. ReactのuseContextっていつ使うの?
  3. ReactのuseContextの使い方
  4. useContext使用前
  5. useContext使用後
  6. useContextでstateの値を使う方法
  7. useContext使用後
  8. useContextと再レンダリング
  9. createContextとuseContextの実例

ReactのuseContextについて、使い方や、いつ使うのかといった情報を共有します。
また、useContextを使って別ファイルとデータ共有の仕方や再レンダリングについても紹介します。

ReactのuseContextについて

useContextを使うことで、値をグローバルに管理できます。
使い方を先にまとめると、親でcreateContext()を作り、<MyContext.Provider value={...}>で子をラップし、使いたいコンポーネントでuseContext(MyContext)を呼ぶ、の3ステップです。
propsを経由せずに、ツリーの深い位置のコンポーネントから直接値を読めるのが利点です。
テーマ、ログイン中のユーザー、言語設定のように「多くのコンポーネントが参照するが頻繁には変わらない値」に向いています。
使い方は後述するとして、まずはいつ使うのかといった使い所を確認します。

スポンサーリンク

ReactのuseContextっていつ使うの?

ReactのuseContextは、値をグローバルに管理したいときです。
具体的なケースとしては、以下のようなケースです。

Sample.js(親コンポーネント)
components/Child.js(子コンポーネント)
components/GrandChild.js(孫コンポーネント)

親コンポーネントで設定した値を、子コンポーネントでは使用せず、孫コンポーネントで使用したいとします。
その場合、useContextを使わない場合はpropsでバケツリレーして、子コンポーネントにpropsで渡して孫コンポーネントで使用します。
これをpropsのバケツリレーといいます。
propsのバケツリレーが増えてくると、
間にあるコンポーネントは使わないpropsを渡すだけになり、どのpropsをどのファイルで使っているのかがわかりにくくなります。
このようなときに、useContextを使います。

逆に、親子1段だけの受け渡しや、そのコンポーネント内でしか使わない値にuseContextを使う必要はありません。
propsのほうがデータの流れが追いやすく、再利用もしやすいからです。
バケツリレーが2〜3段で済むなら、propsのchildrenで中間コンポーネントを挟まない構成にする手もあります。
では実際にコードで確認します。

ReactのuseContextの使い方

useContext使用前

Sample.js(親コンポーネント)

import Child from "./components/Child";

const Sample = () => {
  const value = '孫コンポーネントで使いたい値'
  return <Child value={value}/>;
};

export default Sample;

components/Child.js(子コンポーネント)

import GrandChild from "./GrandChild";

const Child = ({ value }) => (
  <div>
    <h3>子コンポーネント</h3>
    <GrandChild value={value} />
  </div>
);

export default Child;

components/GrandChild.js(孫コンポーネント)

const GrandChild = ({ value }) => {
  return (
      <div>
        <h3>孫コンポーネント</h3>
        {value}
      </div>
  );
};
export default GrandChild;

useContext使用後

Sample.js(親コンポーネント)

import { createContext } from "react";
import Child from "./components/Child";
export const MyContext = createContext("孫コンポーネントで使いたい値");

const Sample = () => {
  return <Child />;
};

export default Sample;

まず、useContextで値を使用するためには、親のコンポーネントで渡したい値をcreateContextで指定する必要があります。
子コンポーネントにはpropsを渡す必要がなくなります。
createContextの引数は「Providerで囲まれていないときに使われるデフォルト値」です。
この例ではProviderを使っていないため、そのデフォルト値がそのまま孫に届いています。
実際のアプリでは次の節のようにProviderで値を渡すのが基本です。

components/Child.js(子コンポーネント)

import GrandChild from "./GrandChild";

const Child = () => (
  <div>
    <h3>子コンポーネント</h3>
    <GrandChild />
  </div>
);

export default Child;

子コンポーネントでは使わないpropsを受け取る必要も渡す必要もなくなり、よりスッキリしたコードになります。

components/GrandChild.js(孫コンポーネント)

import { useContext } from "react";
import { MyContext } from "../Sample";

const GrandChild = () => {
  const value = useContext(MyContext);

  return (
    <div>
      <h3>孫コンポーネント</h3>
      {value}
    </div>
  );
};
export default GrandChild;

孫コンポーネントで使いたいので、createContextで作成された変数を読み込みます。
そして、useContextをreactからインポートします。
あとは、useContextに使いたい値をセットして、それを変数として使うだけです。
これが一番シンプルな使い方となります。

useContextでstateの値を使う方法

Sample.js(親コンポーネント)←ここでcreateContext
Child.js(子コンポーネント)
GrandChild.js(孫コンポーネント)←ここでuseContext
OtherChild.js(他の子コンポーネント)←ここでuseContext
ある親コンポーネントの孫コンポーネントと他の子コンポーネントで同じstateを使いたい場合の書き方をみてみましょう。

useContext使用後

Sample.js(親コンポーネント)

import { useState, createContext } from "react";

import Child from "./components/Child";
import OtherChild from "./components/OtherChild";
export const MyContext = createContext();

const Sample = () => {
  const [state, setState] = useState(0);

  return (
    <MyContext.Provider value={[state, setState]}>
      <Child />
      <OtherChild />
    </MyContext.Provider>
  );
};

export default Sample;

createContextのProviderで使いたいコンポーネントをラップします。
そのProviderに渡した値をuseContextで使用できるようになります。
上のように、useStateの左辺を配列のまま、valueに設定します。
useContextは順々に階層を上がって、createContextのProviderがみつかったらその値を取得します。
Providerの外でuseContextを呼ぶとcreateContext()の引数(上の例ではundefined)が返るため、「undefinedを分割代入できない」というエラーが出たら、Providerで囲めているかを確認してください。
React 19では<MyContext.Provider>の代わりに<MyContext value={...}>と直接書けるようになりましたが、従来のProvider構文も引き続き使えます。

Child.js(子コンポーネント)

import GrandChild from "./GrandChild";

const Child = () => (

  <div className="box">
    <h3>子コンポーネント</h3>
    <GrandChild />
  </div>
);
export default Child;

子コンポーネントは孫コンポーネントを読み込みたいだけなので、特にやることはありません。

GrandChild.js(孫コンポーネント)

import { useContext } from "react";
import { MyContext } from "../Sample";
const GrandChild = () => {
  const [state] = useContext(MyContext);

  return (
    <div className="box">
      <h3>孫コンポーネント</h3>
      {state}
    </div>
  );
};
export default GrandChild;

useContextで受け取りたいstateを指定します。
分割代入でやると便利で、受け取るときに自由に名前を変更できます。

OtherChild.js(他の子コンポーネント)

import { useContext } from "react";
import { MyContext } from "../Sample";

const OtherChild = () => {
  const [state, setState] = useContext(MyContext);

  const clickHandler = (e) => {
    setState((prev) => prev + 1);
  };

  return (
    <div>
      <h3>他の子コンポーネント</h3>
      <button onClick={clickHandler}>+</button>
      <h3>{state}</h3>
    </div>
  );
};

export default OtherChild;

こちらも孫コンポーネントと同様です。
更新用関数だけ必要な場合は、const [, setState] = useContext(MyContext);と分割代入で記述します。

useContextと再レンダリング

Providerのvalueが変わると、そのContextをuseContextで読んでいるコンポーネントはすべて再レンダリングされます。
間にある子コンポーネント(上のChild.js)は、Contextを読んでいなければ再レンダリングされません。
ただし、上のようにvalue={[state, setState]}と配列やオブジェクトをその場で作って渡すと、親が再レンダリングされるたびに新しい配列になり、値が同じでも再レンダリングが起きます。
気になる場合はuseMemo(() => [state, setState], [state])でvalueを固定します。
頻繁に更新される値(入力中のテキストや座標など)をContextに入れると、読んでいる全コンポーネントが毎回描画されるため、そうした値はuseStateでローカルに持つか、更新頻度の高い値と低い値でContextを分けます。

createContextとuseContextの実例

モーダルで値をアプリ全体で使えるようにしているカスタムフックの記事を参考にしてみてください。
【React】カスタムフックの作成と使い所と実例

useContextで共有するstateが増え、更新のパターンも複雑になってきたら、Reduxのような状態管理ライブラリを検討する段階です。
判断の目安はReduxとは?Reactで使う意味・不要なケースにまとめています。
親が子のメソッドを直接呼びたいという別方向の共有は、useImperativeHandleの出番です。

スポンサーリンク