[React] Context API

2021-07-01 hit count image

Reactでデータを扱う概念の1つであるContext APIを使う方法について説明します。

概要

Reactでコンポーネントがデータを扱う方法にはPropsStateそしてContextがあります。今回のブログポストではContextに関する概念と使い方を説明します。

ReactでPropsとStateの概念と使い方については下記のリンクを確認してください。

ここで紹介するスースコードは下記のリンクで確認できます。

Context

ReactでPropsとStateは親コンポーネントと子コンポーネント、または1つのコンポーネント中でデータを扱う時使います。このPropsとStateを使う場合、親コンポーネントから子コンポーネント、つまり上から下、片側にデータが流れるようになります。

React data flow with Props

もし、他のコンポーネントで片側で流れてるデータを使いたい場合、または他のコンポーネントが使ってるデータを現在のデータ流れに入れたい場合はどうすればいいでしょうか?

React need data in another data flow

Reactではデータは上から下に流れるので、使いたいデータとこのデータを使うコンポーネントの共通親コンポーネントにStateを作って使いたいデータをPropsで子コンポーネントに渡せば問題を解決することができます。

React global data with props and state

しかし、このようにコンポーネント中で共有されるデータのため毎回共通親コンポーネントを修正して全ての子コンポーネントにデータをPropsで渡すことは非常に非効率です。このような問題を解決するためReactではFluxと言う概念を導入し、それに合うContext APIを提供を始めました。

Contextは親コンポーネントから子コンポーネントに渡されるデータの流れとは関係なくグローバル的なデータを扱う時使います。グローバルデータをContextに保存した後、データが必要なコンポーネントで当該データを取ってきて使います。

React context

ReactでContextを使うためにはContext APIを使う必要があり、ContextのProviderConsumerを使う必要があります。

React context with Provider and Consumer

Contextで保存されたデータを使うためには共通親コンポーネントにContextのProviderを使ってデータを提供いて、データを使うコンポーネントではContextのConsumerを使って実際データを使います。

Contextの使い方

そしたら今からContext APIを使ってReactでグローバルデータを扱う方法について説明します。

プロジェクト準備

次のコマンドを実行してContextを使うためReactプロジェクトを生成します。

npx create-react-app context_example --template=typescript

Contextの生成

そして、グローバルデータを保存するためContextを生成してみましょう。./src/Contexts/Count/index.tsxファイルを生成して下記のように修正します。


import { createContext, useState } from 'react';

const CountContext = createContext({
  count: 0,
  plusCount: () => {},
});

interface Props {
  children: JSX.Element | JSX.Element[];
}

const CountProvider = ({ children }: Props): JSX.Element => {
  const [count, setCount] = useState(0);

  const plusCount = (): void => {
    setCount(count + 1);
  };

  return (
    <CountContext.Provider
      value={{
        count,
        plusCount,
      }}>
      {children}
    </CountContext.Provider>
  );
};

export { CountContext, CountProvider };

ReactでContextを生成するためにはcreateContextを使います。また、Contextも1つのReactコンポーネントなので、コンポーネント中で変更可能なデータを扱うためStateを使う必要があります。

import { createContext, useState } from 'react';

このように追加したcreateContextを使ってContextを生成します。この時、グローバルで使えるデータの初期値を設定する必要があります。

const CountContext = createContext({
  count: 0,
  plusCount: () => {},
});

Contextも1つのReactコンポーネントなので、基本的にはコンポーネントの形をしてます。この時、画面に表示される内容をContextのProviderを包んで提供します。

...
const CountProvider = ({ children }: Props): JSX.Element => {
  ...
  return (
    <CountContext.Provider>
      {children}
    </CountContext.Provider>
  );
};

export { CountContext, CountProvider };

Contextは1つのReactコンポーネントです。したがって、内部的に変更可能なデータを使うためにはuseStateを使ってStateを使う必要があります。


const CountProvider = ({ children }: Props): JSX.Element => {
  const [count, setCount] = useState(0);

  const plusCount = (): void => {
    setCount(count + 1);
  };
  ...
};

このように作ったStateをContextのProviderに提供します。


const CountProvider = ({ children }: Props): JSX.Element => {
  ...
  return (
    <CountContext.Provider
      value={{
        count,
        plusCount,
      }}>
      {children}
    </CountContext.Provider>
  );
};

最後にcreateContextを使って生成したContextとContextのProviderを使って作ったReactコンポーネントをエクスポートします。ContextのProviderを使って作ったReactコンポーネントは共通親コンポーネントに提供する予定で、createContextで生成したContextはデータを消費する時、使う予定です。

export { CountContext, CountProvider };

これでグローバルデータを扱うためContextを生成してみました。これからこのContextを使う方法について説明します。

Provider

上で作ったContextを使うため、共通親コンポーネントであるAppコンポーネントにContextのProviderを提供してみます。ContextのProviderを提供するため、./src/App.tsxファイルを開いて下記のように修正します。

import { CountProvider } from './Contexts/Count';

import { CountLabel } from './Components/CountLabel';
import { PlusButton } from './Components/PlusButton';

function App() {
  return (
    <CountProvider>
      <CountLabel />
      <PlusButton />
    </CountProvider>
  );
}

export default App;

Contextを使ってグローバルデータを使うためには共通親コンポーネントでContextのProviderを使う必要があります。ここで使うと言う意味はContextのProviderで包むことを意味します。

import { CountProvider } from './Contexts/Count';
...
function App() {
  return (
    <CountProvider>
      ...
    </CountProvider>
  );
}

export default App;

このようにCountProviderで包んだ部分ではContext内部で生成したグローバルデータを自由にアクセスすることができます。

...
import { CountLabel } from './Components/CountLabel';
import { PlusButton } from './Components/PlusButton';

function App() {
  return (
    <CountProvider>
      <CountLabel />
      <PlusButton />
    </CountProvider>
  );
}

またContextを使うコンポーネントを作ってないのでエラーが発生してます。今からContextを使うCountLabelコンポーネントとPlusButtonコンポーネントを作ってみましょう。

Consumer

Contextを使ってグローバルデータを使うコンポーネントを作成してみましょう。まず、./src/Components/CountLabel/index.tsxファイルを生成して次のように修正します。

import { useContext } from 'react';
import { CountContext } from '../../Contexts/Count';

export const CountLabel = () => {
  const { count } = useContext(CountContext);
  return <div>{count}</div>;
};

CountLabelは単純にカウントを画面に表示するコンポーネントです。ここで表示するカウントはグローバル変数で、Contextに宣言した値を使う予定です。このようにContextに宣言された値を使う時、ReactのクラスコンポーネントではConsumerを使いますが、関数コンポーネントではuseContextフック(Hook)を使います。

useContextフックを使って私たちが作ったContextであるCountContextをパラメーターで渡すと、createContextで生成した値にアクセスすることができます。

const CountContext = createContext({
  count: 0,
  plusCount: () => {},
});

CountLabelではContextのcount変数だけ使います。

...
export const CountLabel = () => {
  const { count } = useContext(CountContext);
  return <div>{count}</div>;
};

次はContextのcount変数の値を上げるPlusButtonコンポーネントを生成してみましょう。./src/Components/PlusButton/index.tsxファイルを生成して次のように修正します。

import { useContext } from 'react';
import { CountContext } from '../../Contexts/Count';

export const PlusButton = () => {
  const { plusCount } = useContext(CountContext);

  return <button onClick={plusCount}>+ 1</button>;
};

PlusButtonは画面に単純に+ 1と言うボタンを表示して、そのボタンを押した時、Contextのcount値を上げる関数であるplusCount関数をコールするコンポーネントです。

CountLabelと同じようにuseContextフックと私たちが作ったContextを使って、グローバルデータであるcountの値を上げるplusCount関数を取ってきました。このように取ってきた関数をボタンのonClickに連結しました。

確認

Contextを使ってグローバルデータを扱う例題を作ってみました。このように作ったReactプロジェクトを確認するため、次のコマンドを使ってReactプロジェクトを実行します。

npm start

Reactプロジェクトが実行されたら次のように0+ 1ボタンが表示されることが確認できます。

React Context example: Count

+ 1ボタンを押すと、次のようにカウントが上がることが確認できます。

React Context example: Count increased

完了

これでReactでコンポーネントがグローバルデータを扱う方法であるContextについて簡単に説明しました。今回のブログポストの例題ではContextを使わなくてもいいぐらいの小さいプロジェクトだったので、Contextを使う必要性を感じてなかったと思います。しかし、たくさんのコンポーネントを使ってるプロジェクトではContextを使ってデータを共有する場合がありますので、今回の機会でよく勉強して置くと役に立つと思います。

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

Posts