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 では、特に、次の点をご留意ください。
- アプリ全体の状態が単一のオブジェクト(別名「頼できる情報源(Source of truth)」)に格納されます。
- 状態を変更するには、発生する必要があることを説明する
actions
をディスパッチする必要があります。 - オブジェクトのプロパティを変更したり、既存の配列を変更することはできません。Redux では、新しいオブジェクトまたは新しい配列に常に新しい参照を返さなければなりません。
Redux についてあまりご存知でない方や詳細を学びたい方は、Redux についての実用的なチュートリアルをご覧ください。
React Context API の導入
React Context API はあらゆるレベルに手動で props
を伝えなくても、コンポーネント ツリーを通してデータを送信する方法を提供します。React では、親コンポーネントからその子コンポーネントにプロパティとして送信されることがよくあります。
新しい React Context API を使うことは次の 3 つのステップによって決まります。
- 初期状態を
React.createContext
に送ります。この機能はProvider
やConsumer
と共にひとつのオブジェクトを返します。 Provider
コンポーネントをツリーのトップで使用し、value
と呼ばれるプロパティを承諾させます。この値は何でも構いません。- 状態のサブセットを取得するために、
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 と 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 を置き換えます)。また、state
、props
、および通常の 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 を使うと、状態の変化が示されたロジックを処理するために reducers
、actions
のようなコードをたくさん書く必要がありません。