TL;DR: React Context API は React のエコシステムでは新しいものではありません。ただし、React  の  16.3.0  リリース では  API に多くの改善がなされています。これら改善は予想以上のもので、Redux やその他高度な状態を管理するライブラリの必要性が大きく提言されます。本書では、実用的なチュートリアルを通して新しい React Context API が小型の React アプリケーションに対する Redux への必要性をどのように置き換えるかを学んでいきます。

「この実用的なチュートリアルでどのように Redux から新しい React Context API に移行するかについて学びましょう」

Redux についてのクイックレビュー

React Context API に進む前に、両者を比較できるように Redux についてのクイックレビューをする必要があります。Redux は状態の管理を容易にする JavaScript ライブラリです。Redux は React 自体には結びつきません。世界中の開発者は  React  や  Angular  といった人気の高い JavaScript フロントエンド フレームワークと共に Redux を使ってきました。

誤解のないように言うと、本書で言う状態の管理とはシングルページアプリ(SPA)で発生する特定のイベントが発生したときの変更を処理することです。例えば、ボタンのクリックやサーバーから来る非同期メッセージのようなイベントがアプリの状態への変更をトリガーします。

Redux では、特に、次の点をご留意ください。

  1. アプリ全体の状態が単一のオブジェクト(別名「頼できる情報源(Source of truth)」)に格納されます。
  2. 状態を変更するには、発生する必要があることを説明する  actions  をディスパッチする必要があります。
  3. オブジェクトのプロパティを変更したり、既存の配列を変更することはできません。Redux では、新しいオブジェクトまたは新しい配列に常に新しい参照を返さなければなりません。

Redux についてあまりご存知でない方や詳細を学びたい方は、Redux についての実用的なチュートリアルをご覧ください。

React Context API の導入

React Context API はあらゆるレベルに手動で props  を伝えなくても、コンポーネント ツリーを通してデータを送信する方法を提供します。React では、親コンポーネントからその子コンポーネントにプロパティとして送信されることがよくあります。

新しい React Context API を使うことは次の 3 つのステップによって決まります。

  1. 初期状態を React.createContext に送ります。この機能は  Provider  や  Consumer  と共にひとつのオブジェクトを返します。
  2. Provider  コンポーネントをツリーのトップで使用し、value  と呼ばれるプロパティを承諾させます。この値は何でも構いません。
  3. 状態のサブセットを取得するために、Consumer  コンポーネントをコンポーネント ツリーにあるプロバイダーの下で使います。

ご覧のように、関係する概念は Redux とはそんなに異なりません。実際、Redux はそのパブリック API の下で React Context API を使用します。ただし、最近 Context API は一般人でも十分使用できるほどその成熟度が高まりました。

React アプリを Redux で作る

前述したように、本書の目的は新しい Context API を小さなアプリ向け Redux と置き換える方法をお伝えすることです。よって、シンプルな React アプリを Redux で作り、その後、React Context API を活用できるようにこの状態管理ライブラリを削除する方法について学んでいきます。

これから構築するシンプルなアプリケーションは人気の高い食べ物やその起源のリストを処理するアプリです。このアプリには、ユーザーがあるキーワードを基にしたリストをフィルターできる検索機能も含まれています。

最終的に、次のようなアプリになります。

プロジェクトの要件

本書では React と一部の NPM ライブラリのみを使用しますので、開発マシンには Node.js と NPM がインストールされていれば大丈夫です。Node.js や NPM がまだインストールされていない場合は、公式のインストール手順をご覧いただき、両方をインストールしてください。

これら依存関係をインストールしたら、create-react-app  ツールをインストールする必要があります。このツールは開発者が React で作業を開始するときに役立ちます。ですから、これをインストールするには、端末を開いて、次のコマンドを実行します。

npm i -g create-react-app

React アプリをスキャフォールディングする

create-react-app  をインストールしたら、プロジェクトを配置するディレクトリを移動し、次のコマンドを実行します。

create-react-app redux-vs-context

数秒後、create-react-app  がアプリの作成を終えます。その後、このツールで作成した新しいディレクトリに移動し、Redux をインストールします。


# プロジェクトに移動します
cd redux-vs-context

# Redux をインストールします
npm i --save redux react-redux

注: redux  はメインライブラリで、react-redux  は React と Redux の間の対話を容易にするライブラリです。簡単に言うと、後者は React と Redux の間でプロキシとして機能します。

React アプリを Redux で作る

これで React アプリの構成ができ、Redux をインストールしたので、お好みの IDE でプロジェクトを開きます。そこから、次の3つのファイルを  src  ディレクトリに創ります。

  • foods.json:このファイルは食べ物の静的配列とその起源を保持します。
  • reducers.js:このファイルはアプリの Redux バージョンの状態を管理します。
  • actions.js:このファイルはアプリの Redux バージョンの状態で変更ををトリガーする機能を保持します。

では、始めるには  foods.json  ファイルを開き、次のコンテンツをそれに加えます。

[
  {
    "name": "Chinese Rice",
    "origin": "China",
    "continent": "Asia"
  },
  {
    "name": "Amala",
    "origin": "Nigeria",
    "continent": "Africa"
  },
  {
    "name": "Banku",
    "origin": "Ghana",
    "continent": "Africa"
  },
  {
    "name": "Pão de Queijo",
    "origin": "Brazil",
    "continent": "South America"
  },
  {
    "name": "Ewa Agoyin",
    "origin": "Nigeria",
    "continent": "Africa"
  }
]

ご覧のように、このファイルには特に特別なものはありません。単なる異なる国の異なる食べ物の配列です。

Foods.json  ファイルを定義した後、Redux ストアを定義することを重点的に取り組みます。まとめると、store  はアプリの状態の信頼できる唯一の情報源(Single source of truth)を保持する場所です。そこで、reducers.js  ファイルを開き、次のコードを加えます。

import Food from "./foods";

const initialState = {
  food: Food,
  searchTerm: ""
};

export default function reducer(state = initialState, action) {
  // アクションの種類によって切り替えます
  switch (action.type) {
    case "SEARCH_INPUT_CHANGED":
      const { searchTerm } = action.payload;
      return {
        ...state,
        searchTerm: searchTerm,
        food: searchTerm
          ? Food.filter(
              food =>
                food.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1
            )
          : Food
      };
    default:
      return state;
  }
}

上記のコードでは、ご覧のように state  と  action  の2つのパラメーターを受ける  reducer  機能があります。React アプリケーションを始めるとき、この機能はその直前で定義された  initialState  を取得し、アクションのインスタンスをディスパッチするとき、この機能は現在の状態(もはや  initialState  ではない)を取得します。それから、これらアクションのコンテンツを基に、reducer  機能はアプリの新しい状態を作ります。

次に、これらアクションが何かを定義します。実際、これをシンプルにするには、ユーザーがアプリに検索語句を入力したときにトリガーする1つのアクションを定義します。そこで  actions.js  ファイルを開き、次のコードを挿入します。

function searchTermChanged(searchTerm) {
  return {
    type: "SEARCH_INPUT_CHANGED",
    payload: { searchTerm }
  };
}

export default {
  searchTermChanged
};

これで  action  クリエータができましたから、次にやることは  react-redux  で利用可能な  App  コンポーネントを  Provider  コンポーネントにラップすることです。このプロバイダーは React アプリを信頼できる唯一の情報源(Single source of truth)(例:store)にする担当です。

このプロバイダーを使うには、まず、reducers.js  ファイルで定義された  initialState  を使ってアプリの  store  を作ります。それからこの  store  を  Provider  の助けを得て  App  に送ります。これら作業を達成するには、index.js  ファイルを開き、そのコンテンツと次を置き換えます。

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducers from "./reducers";
import App from "./App";

// Rducer 情報を使ってストアを作ります。
// それは Rducer が Redux Store の文書パーツだからです。
const store = createStore(reducers);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

これだけです!これで React アプリに Redux を構成し終えました。ここで、UI(ユーザー インターフェイス)を実装しますから、ユーザーはこの章で実装される機能を使用します。

React インターフェイスを構築する

これでアプリケーションのコアが出来上がったので、ユーザー インターフェイスの構築に取り組みます。そのためには  App.js  ファイルを開き、そのコンテンツと次を置き換えます。

import React from "react";
import { connect } from "react-redux";
import actions from "./actions";
import "./App.css";

function App({ food, searchTerm, searchTermChanged }) {
  return (
    <div>
      <div className="search">
        <input
          type="text"
          name="search"
          placeholder="Search"
          value={searchTerm}
          onChange={e => searchTermChanged(e.target.value)}
        />
      </div>
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Origin</th>
            <th>Continent</th>
          </tr>
        </thead>
        <tbody>
          {food.map(theFood => (
            <tr key={theFood.name}>
              <td>{theFood.name}</td>
              <td>{theFood.origin}</td>
              <td>{theFood.continent}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

export default connect(
  store => store,
  actions
)(App);

Redux を使ったことがない方がご存知ないかもしれないのは  App  コンポーネントをカプセル化するために使用する  connect  機能だけです。この機能は高次コンポーネント(HOC) で、アプリと Redux  をつなげる接着剤のような役目を果たします。

ここでアプリを実行すれば、ブラウザーで使用できます。

npm run start

ただし、ご覧のようにアプリの見掛けはよくありません。そこで、それを改善するために、App.css  ファイルを開き、そのコンテンツと次を置き換えます。

table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 15px;
  line-height: 25px;
}

th {
  background-color: #eee;
}

td,
th {
  text-align: center;
}

td:first-child {
  text-align: left;
}

input {
  min-width: 300px;
  border: 1px solid #999;
  border-radius: 2px;
  line-height: 25px;
}

React app implemented with Redux

これだけです!これで基本的な React と Redux のアプリができたので、Context API をどのように移行するかについて学びます。

React アプリを React Context API で実装する

本章では、アプリの Redux バージョンを React Context API に移行する方法について学んでいきます。

幸運なことに、ご覧のように、Redux と Context API 間を切り替えて何度もリファクタリングする必要はありません。

初心者のために、アプリから Redux のあらゆるトレースを削除する必要があります。そのためには、端末から  redux  と  react-redux  のライブラリを両方とも削除します。

npm rm redux react-redux

その後、これらライブラリを参照する  import  ステートメントを削除します。ですから  App.js  ファイルを開き、次の行を削除します。

import { connect } from "react-redux";
import actions from "./actions";

それから、このファイルの最後の行(export default で始まる行)と次を置き換えます。

export default App;

これら変更が終わったら、Context API でアプリを再書き込みします。

Redux から React Context API へ移行する

Redux を利用した元のアプリから Context API を利用するアプリに変えるには、アプリのデータを格納するコンテキストが必要です(このコンテキストが Redux Store を置き換えます)。また、stateprops、および通常の React コンポーネント ライフサイクルがある Context.Provider  コンポーネントが必要です。

よって、src  ディレクトリに  providers.js  ファイルを作り、次のコードを加えます。

import React from 'react';
import Food from './foods';

const DEFAULT_STATE = { allFood: Food, searchTerm: '' };

export const ThemeContext = React.createContext(DEFAULT_STATE);

export default class Provider extends React.Component {
  state = DEFAULT_STATE;
  searchTermChanged = searchTerm => {
    this.setState({searchTerm});
  };

  render() {
    return (
      <ThemeContext.Provider value={{
        ...this.state,
        searchTermChanged: this.searchTermChanged,
      }}> {this.props.children} </ThemeContext.Provider>);
  }
}

上記のコードで定義した  Provider  クラスは  ThemeContext.Provider 内にその他のコンポーネントをカプセル化する担当です。そうすることで、これらコンポーネントがアプリの状態や、この状態を変更する方法を提供する  searchTermChanged  機能にアクセスできます。

コンポーネント ツリーの値を後で消費するには、ThemeContext.Consumer  コンポーネントを始める必要があります。このコンポーネントは思いのままに使用する引数として上記の値  props  を受けるレンダー関数が必要です。

では、次に、src  ディレクトリ内に  consumer.js  と呼ばれるファイルを作り、次のコードを書き込みます。

import React from 'react';
import {ThemeContext} from './providers';

export default class Consumer extends React.Component {
  render() {
    const {children} = this.props;

    return (
      <ThemeContext.Consumer>
        {({allFood, searchTerm, searchTermChanged}) => {
          const food = searchTerm
            ? allFood.filter(
              food =>
                food.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1
            )
            : allFood;

          return React.Children.map(children, child =>
            React.cloneElement(child, {
              food,
              searchTerm,
              searchTermChanged,
            })
          );
        }}
      </ThemeContext.Consumer>
    );
  }
}

ここで、移行を終わらせるために、index.js  ファイルを開き、 render()  関数内で  App  コンポーネントを  Consumer  コンポーネントでラップします。また、Provider  コンポート内で  Consumer  をラップします。そうすると、次のようになります。

import React from "react";
import ReactDOM from "react-dom";
import Provider from "./providers";
import Consumer from "./consumer";
import App from "./App";

ReactDOM.render(
  <Provider>
    <Consumer>
      <App />
    </Consumer>
  </Provider>,
  document.getElementById("root")
);

これだけです!これで Redux から React Context API への移行が終わりました。今、アプリを実行すると、全体が以前のように機能していることが分かります。ここでの違いはアプリは Redux を使っていないということです。

「新しい React Context API は小さな React アプリケーションの Redux に代わる素晴らしいものです。」

まとめ

Redux は大規模な React アプリを構築するときに使用すべき、高度な状態の管理ライブラリです。一方、Context API は小規模の React アプリで使用でき、バイト サイズで変更が可能です。Context API を使うと、状態の変化が示されたロジックを処理するために reducersactions のようなコードをたくさん書く必要がありません。