React Context API は Redux の代替となるか


それなりに```React```を使ってきた自負はあったのですが、お恥ずかしい話、メジャーな機能であるところの```Context API```を使ったことがありませんでした。
というのも、すでに「グローバル」的にpropsを扱いたい場合、reduxを使っているので必要性をそれほど感じていなかったというのもあります。

しかし、昨今のReactの実装された機能を見てみると、必ずしもreduxを使う必要性はないのではないかと考えるようにもなり、Context APIもちょっと知っておきたいと思い、投稿した次第です。


# 実装手順

やることのステップは非常に簡単で、

- データを作成する
- Contextを作成する
- ContextをProvideする
- Contextを利用する

の4ステップで利用可能です。


## データを作成する

Contextの中身となるデータオブジェクトを作成します。
```export default```でどこでもこのデータオブジェクトにアクセスできるようにしています。

```javascript:language.js
const language = {
  en: {
    greet: 'hello',
    name: 'Context'
  },
  ja: {
    greet: 'こんにちは',
    name: 'コンテキスト'
  }
}

export default language;
```

## Contextを作成する

まずは、Reactならびに、先ほど上で作ったデータをインポートします。
その後、Reactクラスのメソッド```createContext(defaultValue)```を使ってコンテキストを作成します。
この例では、言語データオブジェクトの英語をデフォルトとして設定しています。

```javascript:LangContext.js
import React from 'react';
import language from './language';

const LangContext = React.createContext(language.en);
export default LangContext;

```
こちらも```export defaul```tでどこからでも参照できるようにします。

## ContextをProvideする

作成したContextを各コンポーネントに行き渡らせるために以下のような手順を踏みました。

1. React Hookの```useState```をインポート
2. Contextに渡す用のStateを作成
3. LangContext.Provider
4. Contextに渡すStateをonClickで切り替えるtoggle関数作成

コードは以下のような感じ。


```jsx:App.js
import React, { useState } from 'react';

import language from './language';
import LangContext from './LangContext';
import Greeting from './Greeting';

const App = props => {
  const [lang, setLang] = useState(language.en);

  const toggle = () => {
    if (lang === language.en) {
      setLang(language.ja);
    } else {
      setLang(language.en);
    }
  };
  return (
    <>
      <LangContext.Provider value={lang}>
        <Greeting/>
        <button onClick={e => toggle()}>Toggle！</button>
      </LangContext.Provider>
    </>
  );
}

export default App;

```

```<LangContext.Provider value={//}>```の子要素がコンテキストの適用範囲となります。上記の例ではGreetingというコンポーネントがコンテキストを継承することができます。

valueの値が変わるたびに、Greeting以下のコンポーネントに対して再描画がかかります。

## Contextを利用する

子要素がどのようにContextを受け取り利用するかについて書いていきます。

1. React Hookで追加された```useContext```をインポート
2. 上で作成したコンテキストをインポート
3. ```useContext``` の引数にコンテキストを渡す
4. あとは、```value.プロパティ```という形でオプジェクトにアクセス


```jsx:Greeting.js
import React, { useContext } from 'react';
import LangContext from './LangContext';
import Name from './Name';

const Greeting = props => {
  const value = useContext(LangContext);
  return (
    <>
      <div>{value.greet}</div>
      <Name/>
    </>
  );
}

export default Greeting;
```



# 下位ツリーからデータを変更する方法

上記の例では、親コンポーネントでデータを切り替えていましたがツリーの下位からデータを変更する方法について書いていきます。

主な流れは以下の通りです。

- データを作成する
- Contextを作成する
- ContextをProvideする
- Consumerを作成する ← 追加
- Contextを利用する

## データを作成する

ここはほとんど変わりません。toggle用のボタンの文言を変更するために、プロパティを一つ追加しただけです。

```javascript:language.js
const language = {
  en: {
  	greet: 'hello',
    name: 'Context',
    btn: 'translate to Japanese'
  },
  ja: {
  	greet: 'こんにちは',
    name: 'コンテキスト',
    btn: '英語に翻訳する'
  }
}

export default language;

```

## Contextを作成する

データと、関数の２つをプロバイドする必要があるので、デフォルト値をオブジェクト形式にして、データと関数を定義します。

```javascript:LangContext.js
import React from 'react';
import language from './language';

const LangContext = React.createContext({
  lang: language.en,
  toggleLang: () => {}
});
export default LangContext;
```

## ContextをProvideする

ここもほとんど変わっていませんが、一点注意があります。
```LangContext.Provider```のvalueをオブジェクトとして渡しています。

```jsx:App.js
import React, { useState } from 'react';

import language from './language';
import LangContext from './LangContext';
import Greeting from './Greeting';
import LangToggle from './LangToggle';

const App = props => {
  const [lang, setLang] = useState(language.en);

  const toggle = () => {
    if (lang === language.en) {
      setLang(language.ja);
    } else {
      setLang(language.en);
    }
  };

  return (
    <>
      <LangContext.Provider value={{lang: lang, toggleLang: toggle}}>
        <Greeting/>
        <LangToggle/>
      </LangContext.Provider>
    </>
  );
}

export default App;

```


## Consumerを作成する

ここが一番大きく変わった点です。

```LangContext.Consumer```のスコープ配下でContext のvalue（lang, toggleLang）を受け取ることができるので、button要素のクリックイベントにtoggleLangをセットします。
ついでに、ボタンの文言も変更したいので、　```{lang.btn}```と指定しています。


```jsx:LangToggle.js

import React from 'react';
import LangContext from './LangContext';

const LangToggle = () => {
  return (
    <LangContext.Consumer>
      {({lang, toggleLang}) => {
        return (
          <button
            onClick={toggleLang}
            >
            {lang.btn}
          </button>
        );
      }}
    </LangContext.Consumer>
  );
};

export default LangToggle;
```

## Contextを利用する

ここもほとんど変更点はありません。コンテキストのデータがオブジェクト形式になったので、少しだけ指定方法が変わります。

```jsx:Greeting.js

import React, { useContext } from 'react';
import LangContext from './LangContext';
import Name from './Name';

const Greeting = props => {
  const value = useContext(LangContext);
  return (
    <>
      <div>{value.lang.greet}</div>
      <Name/>
    </>
  );
}

export default Greeting;
```


以上で完成です！


![context.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251245/c584dcf1-bad2-6ae4-ca36-96e3c103151e.gif)



## 所感


reduxでももちろん同じことはできますが、diapatcherとかactionCreaterとか用意しなければならないので、こちらの方がより簡単だと思います。
もちろん、ビジネスロジックが入り組んだ処理などはreduxに任せた方が良いと思います。

つまり、

- 滅多に変更がかからないが、変更すると広い範囲に影響を及ぼすものにはContext
- 頻繁にデータが変更され、一つ一つは局所的な影響を及ぼすものについてはRedux

が向いているのではないかと思います。
個人的にはContextはReduxの代替にはならないが、Context とReduxは共存できるかもしれないと考えていいます。