【React】useContextについて分かりやすく解説!

本記事はこんな方のために書いています。

  • 「useContext」ってなんで必要なの?と思ってる方
  • 関数コンポーネントでpropsの渡し方を復習したい方
  • 「propsドリリング問題」について知りたい方

Reactには、props(変数)の受け渡しを便利にする「useContext」という機能があります。多くの記事では、useContextの使い方を説明しているだけで、なぜ「useContextを使わないといけないのか?」「どんなメリットがあるのか?」を触れていない印象です。

そこで今回はuseContextを「使わない場合」と「使った場合」の2つのコードを紹介します。「useContext使わないとめっちゃめんどいやん!」と思ってもらえるような記事にしたので、ぜひ参考にしてくださいね。

完成形を確認しよう

今回作成するアプリがこちらです。コンポーネントの中にコンポーネントがあり、その中に別のコンポーネントがある構造になっています。

完成形

親コンポーネント(App)とひ孫コンポーネント(ComponentC)のボタンは異なります。親のボタンは、countに1を足すだけですが、ひ孫のボタンはcountに2倍するようになっています。

Reactを使う準備をしよう

何はともあれ、Reactを使う準備をしていきます。Reactでアプリを作るにあたって、必要なフォルダを1から作成する必要はありません。

「create-react-app」という便利な機能を使うと、Reactアプリの雛形を作成してくれます。

ターミナルで下記コマンドを実行します。

npx create-react-app usecontext_practice

//コメント
↑usecontext_practiceの部分は好きな名前でOKです。

上の処理が終わったら、ターミナルで「yarn start」を実行します。
そうするとブラウザが自動で立ち上がり、「React」ロゴがグルグル回る画面が開きます。

準備が整ったので、次に進みましょう。

useContextを使わずにpropsを渡してみよう

まずはuseContextを使わない方法で行っていきます。ここで「ああprops渡すのめんどくさいな〜」って感じてもらえればOKです。

App.jsでComponentAを呼び出し、props(変数)としてcountとそれを操作するsetCount関数を渡しています。

import "./App.css";
import "./index.css";
import React, { useState } from "react";
import ComponentA from "./components/ComponentA";

function App() {
  const [count, setCount] = useState(1);
  return (
    <div className="App">
      <div className="btn-area">
        <p>(親)AppComponentやで。</p>
        <span>{"count : "}</span>
        <span>{count}</span>
        <br />
        <button onClick={() => setCount(count + 1)}>1を足すボタンだよ。</button>
        <button onClick={() => setCount(1)}>リセットするよ。</button>
      </div>
      <ComponentA count={count} setCount={setCount} />
    </div>
  );
}

export default App;

次はsrcフォルダの直下に「components」フォルダを作成してください。
その配下に「ComponentA.js」「ComponentB.js」「ComponentC.js」を作成し、下記コードを貼り付けましょう。

ComponentAでは、親であるApp.jsから渡ってきたprops(countとsetCount)をさらにComponentBにpropsとして渡しています。

AはpropsをBに渡すだけで、Aの中でpropsの値を使用していません。(ここ重要!)

ちなみに引数が{count , setCount}となっているのが気持ち悪いと感じた方もいるかもしれません。本来の形は、{ count: count, setCount: setCount }ですが、Javascriptにはキーとバリューが同じ名前なら、{count , setCount}のように省略できるというルールがあるためです。

import React from "react";
import ComponentB from "./ComponentB";

const ComponentA = ({ count, setCount }) => {
  return (
    <div className="component-a">
      <p>(子供)Component Aやで。</p>
      <ComponentB count={count} setCount={setCount} />
    </div>
  );
};

export default ComponentA;

親であるAから渡ってきたpropsをCに渡しています。
Bはpropsを渡すだけで、Bの中でpropsの値を使用していません。(ここ重要!)

import React from "react";
import ComponentC from "./ComponentC";

const ComponentB = ({ count, setCount }) => {
  return (
    <div className="component-b">
      <p>(孫)Component Bです。</p>
      <ComponentC count={count} setCount={setCount} />
    </div>
  );
};

export default ComponentB;

App.jsで定義したpropsがようやく、App→A→B→Cの順で渡ってきました。

import React from "react";

const ComponentC = ({ count, setCount }) => {
  return (
    <div className="component-c">
      <p>(ひ孫)Component Cです。</p>
      <div className="btn-area">
        <span>{"count : "}</span>
        <span>{count}</span>
        <br />
        <button onClick={() => setCount(count * 2)}>2倍するボタンだよ。</button>
        <button onClick={() => setCount(1)}>リセットするよ</button>
      </div>
    </div>
  );
};

export default ComponentC;

あとは、CSSでデザインを整えます。
App.jsにはデフォルトで色々と設定されていますが、必要なので下記コードを貼り付けてください。

.App {
  text-align: center;
  background-color: lightcoral;
  padding:30px;
}

次にindex.cssに下記コードを追加してください。

.component-a {
  background-color: aqua;
  padding: 30px;
  margin: 30px;
}

.component-b {
  background-color: greenyellow;
  padding: 30px;
}

.component-c {
  background-color: lightblue;
  padding: 30px;
}

.btn-area {
  margin: 0px 30px 10px 30px;
  padding: 30px;
}

ここまでで下画像のようになっていると思います。

ここまでコードを書いてきて気づいた方もいるかもしれませんが、コンポーネントAとBは、その中でpropsを使わず、ただ受け渡してるだけなのです。

これが通称「prop drilling問題」です。
本来であれば、AppからCへ直接propsを渡せば良いのに、無駄な受け渡しが発生しています。

この問題を解決してくれるのが、「useContext」です。

useContextを使ってコードを書き換えてみよう

まずはcomponentsフォルダと同階層に、「contexts」フォルダを作成します。
その配下に「AppContext.js」ファイルを作成し、下記コードを貼り付けてください。

ファイルの名前は、好きにつけてOKですが、〇〇Context.jsという形にしましょう。またContextに関するファイルは「contexts」というフォルダ内に作成すると、管理がしやすいです。

import { createContext } from "react";

const AppContext = createContext();

export default AppContext;

次にuseContextを使用していきます。下記のように、<コンテキスト名.Provider value={props名}><コンテキスト名.Provider>で子供のコンポーネントを囲むことで、囲んだものにpropsが渡させれます。

<AppContext.Provider value={props名}>
 <propsを渡したいコンポーネント/>
</AppContext.Provider>

ComponentAだけでなく、BやCにもpropsは伝播していきます。
useContextを使っているので、AからBにpropsを渡す必要がありません。


import "./App.css";
import "./index.css";
import React, { useState } from "react";
import ComponentA from "./components/ComponentA";
import AppContext from "./contexts/AppContext";


function App() {
  const [count, setCount] = useState(1);
  return (
    <div className="App">
      <AppContext.Provider value={[count, setCount]}>
        <div className="btn-area">
          <p>(親)AppComponentやで。</p>
          <span>{"count : "}</span>
          <span>{count}</span>
          <br />
          <button onClick={() => setCount(count + 1)}>
            1を足すボタンだよ。
          </button>
          <button onClick={() => setCount(1)}>リセットするよ。</button>
        </div>
        <ComponentA />
      </AppContext.Provider>
    </div>
  );
}
export default App;

useContextを使っているので、BからCにpropsを渡す必要がありません。

import React from "react";
import ComponentB from "./ComponentB";

const ComponentA = () => {
  return (
    <div className="component-a">
      <p>(子供)Component Aやで。</p>
      <ComponentB />
    </div>
  );
};

export default ComponentA;
import React from "react";
import ComponentC from "./ComponentC";

const ComponentB = () => {
  return (
    <div className="component-b">
      <p>Component Bです。</p>
      <ComponentC />
    </div>
  );
};

export default ComponentB;

propsのバケツリレーをすることもなく、propsであるcountやsetCount関数をCで使用することができます。

import React, { useContext } from "react";
import AppContext from "../contexts/AppContext";

const ComponentC = () => {
  const [count, setCount] = useContext(AppContext);
  return (
    <div className="component-c">
      <p>(ひ孫)Component Cです。</p>
      <div className="btn-area">
        <span>{"count : "}</span>
        <span>{count}</span>
        <br />
        <button onClick={() => setCount(count * 2)}>2倍するボタンだよ。</button>
        <button onClick={() => setCount(1)}>リセットするよ</button>
      </div>
    </div>
  );
};

export default ComponentC;

useContextについての解説は以上です。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です