コンテクストフックは、コンポーネント間でデータ(props)を受け渡すための仕組みです。

Webサイトのテーマ(ダークモード/ライトモードなど)の切り替えや、ユーザー認証状態の監視などを実装する際に使います。

Contextを使えば、以下のようなアプリを実装できます。


useContextで作ったタスク管理アプリ


本記事で、useContextの基本的な使い方をマスターしておきましょう。


1.コンテクストとは

コンポーネント間でデータを共有する場合、通常はpropsが使われます。

親と子の間に中間コンポーネントが存在する場合(親→子→孫)でも、同様にpropsを使います。

これは、Reactの「単方向データフロー(親から子方向へのデータの受け渡し)」という設計上、正しいデータの流れです。

ただし、



という場合、propsを使ったデータの受け渡しは、コードの可読性や保守性が低くなります。


https://res.cloudinary.com/dxmbpq15j/image/upload/f_auto,q_auto,w_1200,h_630,c_fill/1_ekjeal.jpg

一方で、コンテクストフックを使用すれば、親コンポーネントから直接、目当てのコンポーネントにデータを受け渡せるようになります。


https://res.cloudinary.com/dxmbpq15j/image/upload/f_auto,q_auto,w_1200,h_630,c_fill/2_itgwko.jpg

2.コンテクストフックの基本構文

コンテクストフックは、



に分けられます。


2-1.createContext

createContextは、その名の通り、コンテクストを作成する関数です。


createContextの例

const DemoContext =createContext(Value)


2-1-1.createContextの返り値

createContextの返り値(以下の「DemoContext」に当たる要素)は、オブジェクトを返します。


const DemoContext =createContext(Value)


createContextの返り値であるオブジェクトは、以下のような、「プロバイダー(コンポーネントにコンテクストの値を渡すもの)」プロパティを持っています。



使い方としては、親コンポーネントでcreateContextを宣言し、子コンポーネントをこの「プロバイダー」でラップすることで、子コンポーネントにデータを渡します。


const DemoContext =createContext(null);
functionParentComponent(){
  return(
    <DemoContext value="provided value">
      <ChildComponent />
    </DemoContext>
  );
}


プロバイダーのprops「value」

valueに渡した値は、プロバイダーの内側にあるすべての子孫コンポーネントで読み取れます。

valueには、文字列や数値はもちろん、配列・オブジェクト・関数などの型のデータも渡せます。


なお、valueを指定しない、あるいは違う名前のpropsを渡すと、コンテクストは「undefined」を返します。


プロバイダーをネストしている場合の挙動

同じコンテクストをネストした場合、フックの挙動に注意が必要です。

ほぼ使わないケースですが、値をオーバーライドしたい時に使う記法です。

以下のように、呼び出し側のコンポーネントは、一番近いロバイダーのvalueの値を受け取ります。


<DemoContext value="outer">
  <DemoContext value="inner">
    <Child />  //useContext(DemoContext)は「inner」
  </DemoContext>
</DemoContext>



2-1-2.createContextの引数

createContextの引数(以下の「default」に当たる要素)は、コンポーネントがコンテクストを読み込んだ際、プロバイダーが存在しない場合に、コンテクストが返す値です。

実際の開発では、ほぼ「null」値が指定されます。


const DemoContext =createContext(default)


なお、createContextの引数で指定するデフォルト値は、useContext(後で解説します)がプロバイダーを見つけられない場合にのみ使用します。


createContextの引数は、静的な値であり動的に変化しないので注意が必要です。

コンテクストの値を動的に変更したい場合は、コンテクストフックをstate(状態)と組み合わせて使用する必要があります。

2-2.useContext

useContextは、その名の通り、コンテクストを使うための関数です。

正確には、値の読み取りサブスクライブ(値の変更を監視して、読み取り側のコンポーネントを再レンダーする)を行います。


useContextの例

const demo =useContext(demoContext)


2-2-1.useContextの返り値

useContextの返り値(以下の「demo」に当たる要素)には、事前にcreateContextで作成したコンテクストの現在の値が入っています。


const demo =useContext(demoContext)


「2-1-1.createContextの返り値」で解説した通り、同じコンテクストをネストする形でラップしている場合は、一番近いコンテクストに渡されたvalueが返り値に入ります。

また、対応するコンテクストにプロバイダーが存在しない場合は、 createContextの引数を返します。


なお、useContextの返り値が返す値は、静的な値だけではありません。

useContextは、コンテクストの変更を監視し、変化があれば、useContextを使っているコンポーネントを再レンダーします。


2-2-2.useContextの引数

useContextの引数(以下の「demoContex」に当たる要素)には、createContextで作ったコンテクストを渡します。


const demo =useContext(demoContext)


3.コンテクストフックの使用法(静的な値)

まずは、静的な値を受け渡すコードで、コンテクストの基本的な使い方を理解しましょう。


完成版のコード


step1.コンテキストを定義するファイルを作る

まずは、コンテキストを定義したファイル「HogeContext.js」を作ります。

最初に、createContextをインポートしましょう。


HogeContext.js

import{ createContext }from'react';


次に、createContextを呼び出して、HogeContextを定義します。


HogeContext.js

exportconst HogeContext =createContext("default value");
//"default value"はプロバイダーがない場合に返される値。


step2.プロバイダーを渡すファイルを作る

「HogeContext.js」を作成したあとは、プロバイダーを使って値を渡す側のファイル「App.jsx」を作ります。

最初に、先ほど作った「HogeContext」をインポートしましょう。


App.jsx

import{ HogeContext }from"./HogeContext";


次に、空の関数コンポーネントを定義しておきましょう。


App.jsx

constApp=()=>{
  return(
    <>
    </>
  );
}


エクスポートもしておきましょう。


App.jsx

exportdefault App;


step3.コンテキストの値を受け取るファイルを作る

「App.jsx」を作成したあとは、プロバイダーを使って値を渡す側のファイル「Child.jsx」を作ります。

まずは、値を受け取るための「useContext」をインポートしましょう。


Child.jsx

import{ useContext }from"react";


次に、「App.jsx」でも読み込んだ「HogeContext」をChild.jsxにもインポートします。


Child.jsx

import{ HogeContext }from"./HogeContext";


空の関数コンポーネントも定義し、エクスポートしておきましょう。


Child.jsx

constChild=()=>{
  return(
    <>
    </>
  );
}
exportdefault Child;


step4.親コンポーネントで子コンポーネントを読み込んでプロバイダーでラップする

コンテキストを扱うための3つのファイルを用意できたら、次はApp.jsxに子コンポーネント(Child.jsx)をインポートしましょう。


App.jsx

import Child from"./Child";


また、プロバイダーは、値を渡したいコンポーネントをラップする必要があります。

プロバイダーの内側にあるコンポーネントだけがuseContextで値を受け取れます。


そのため、インポートした子コンポーネントを、プロバイダーでラップします。

加えて、valueに「context value」という文字列を渡しておきましょう。


App.jsx

constApp=()=>{
  return(
    <HogeContext value="context value">
      <Child />
    </HogeContext>
  );
}


step5.子コンポーネントで値を受け取る

最後に、コンテキストを使う側のコンポーネント(Child.jsx)で、値を受け取ります。

まずは、Child.jsxでuseContextを呼び出します。


Child.jsx

constChild=()=>{
  const hoge =useContext(HogeContext);
  return(
    <>
    </>
  );
}


次に、useContextで受け取った値を表示してみましょう。


Child.jsx

constChild=()=>{
  const hoge =useContext(HogeContext);
  return(
    <p>
      {hoge}
    </p>
  );
}


問題なくコンテキストを受け取れていれば、画面には「context value」と表示されているはずです。


もし、コンテキストを受け取れていなければ、画面に「default value」と表示されます。

プロバイダーのラップや、valueの設定が間違っていないか確認しましょう。


4.コンテクストフックの使用法(動的な値)

ほとんどの場合、コンテクストでは、動的な値を扱います。

動的な値を扱うためには、コンテクストフックとstateの併用が必要です。


ここでは、先ほど「3.Contextフックの使用法(静的な値)」で解説したコードを使って、コンテクストで動的な値を扱う方法を解説します。


複雑な修正は必要なく、「3.Contextフックの使用法(静的な値)」で解説したコードの「App.jsx 」を修正するだけで、動的な値を受け渡せます。


デモアプリ


完成版のコード


step1.useStateをインポートする

まずは、App.jsxにstateフックをインポートしましょう。


App.jsx

import{ useState }from"react";


step2.useStateを呼び出す

次に、useStateを呼び出して、プロバイダーに渡すstateを作ります。


App.jsx

constApp=()=>{
  const[hoge, setHoge]=useState("initial value");
  return(
    <HogeContext value="context value">
      <Child />
    </HogeContext>
  );
}


step3.valueにstateを渡す

最後に、プロバイダーのvalueに stateを渡します。


App.jsx

constApp=()=>{
  const[hoge, setHoge]=useState("initial value");
  return(
    <HogeContext value={hoge}>
      <Child />
      <button onClick={()=>setHoge("updated value")}>
        値を更新する
      </button>
    </HogeContext>
  );
};


これで、ボタンをクリックすると、


  1. ボタンをクリックする(画面表示は「initial value」)
  2. setHogeが呼ばれる
  3. Appが再レンダーされる
  4. valueが更新される
  5. Childが再レンダーされる
  6. useContextが新しい値を受け取る
  7. 画面が書き換わる(画面表示は「updated value」)


という流れで、コンテキストの値が動的に書き換えられるようになりました。


5.注意点

5-1.useContextの呼び出し位置

同じコンポーネント内で、useContextとcreateContextを呼び出すと、プロバイダーは、valueの値ではなく、createContextの引数を返します。


createContextで作ったプロバイダーは、useContexを使っているコンポーネントの上に配置する必要があります。



5-2.createContextが複数回呼ばれる場合の挙動

reateContextの返り値は、オブジェクトです。

ReactはこのオブジェクトをキーとしてプロバイダーとuseContextを紐づけています。

そのため、プロバイダーとuseContextが同じオブジェクト(内部的には===に比較で一致判定をしています。)を使っていないと、コンテクストは正常に動きません。


モジュールが何らかの理由で複製され、同じファイルが2回読み込まれると、createContextが複数回実行され、異なるコンテクストオブジェクトが生成されます。

そうした場合、プロバイダーとuseContextが別のオブジェクトを参照し、プロバイダーが見つからず、createContextの引数を返すため、意図しない動作を引き起こす可能性があります。


また、ビルドツールの問題によって、同じモジュールが複数コピーされてしまう場合も、プロバイダーとuseContextが異なる参照を持つことになり、コンテクストが正常に動作しないケースがあります。


5-3.memoによる再レンダーの防止

プロバイダーのvalueが変わると、useContextを使っているコンポーネントは、必ず再レンダーされます。


Reactのmemoでラップしたコンポーネントは、propsが前回と同じ値なら再レンダーをスキップできます。

しかし、この再レンダーは、memoを使っても止められません。

参考:https://ja.react.dev/reference/react/memo


6.あとがき

コンテクストは、Reactアプリ全体で共有したい値を、どのコンポーネントからでも取り出せるようにするためのフックです。  


Webアプリ開発では、テーマ設定や認証情報など、複数の画面で共通のデータを扱う場合があります。

コンテクストは、こうした「グローバルに扱いたい状態」を簡単に共有できます。


今回は、コンテクストの基本的な考え方や仕組み、注意点まで解説しました。

ReactでWebアプリ開発するうえで欠かせないフックなので、使い方をしっかりマスターしておきましょう。