React Context入門
React Contextはv16.3で追加された機能です。React Contextを使用すると、複数のコンポーネントで値を共有することができます。
今回は、このReact Contextの使い方を簡単に書いてみます。
どういうときに使用するか
公式に書かれているように、グローバルライクに使用したいpropsがある場合に利用されます。ただし、どこからでもアクセスが可能になるわけではなく、明示的に指定した範囲内でのみ参照が可能です。
Reduxと比較されることもあるようですが、状態を保管するストアが別にあるわけではなく、アプリケーション全体の状態管理をするには向いていません。
凡例
A、B、Cという3つのコンポーネントがあるとします。Aコンポーネントの子にBコンポーネントがあり、Bコンポーネントの子にCコンポーネントがあります。
Aコンポーネントはcountという状態を持ち、この値はCコンポーネントでも利用します。一般的にReactでは親から子に値を渡すにはpropsを使うため、Aのcount値をCでも使うにはB経由でpropsを伝播させる必要があります。
// Aコンポーネントのrender内
<B count={this.state.count} />
// Bコンポーネント内
const B = (props) => <C count={props.count} />;
// Cコンポーネント内(ステートフルの場合)
const count = this.props.count;
Bは単にCコンポーネントをレンダリングするだけで、count値は必要ありません。これは1階層下のため問題はありませんが、100階層下のコンポーネントに値を引き渡す場合は非常に面倒です。これを解決するのがコンテキストです。
コンテキストは指定したpropsを、コンポーネント間で共有させることができます。
利用方法
コンテキストの利用に必要なステップは、3つだけです。
- Contextを作成する
- Providerに受け渡したい値をセットして定義する
- Consumerを定義して値を受け取り処理をする
サンプルプログラム
先の凡例を実際のコードで作成してみます。まずAコンポーネントです。
import React from "react";
import B from "./B";
export const CountContext = React.createContext();
export default class A extends React.Component {
state = {
count: 0
};
render() {
return (
<div>
<CountContext.Provider value={this.state}>
<B />
</CountContext.Provider>
</div>
);
}
}
最初に、React.createContext()
でCountContextというコンテキストを作成しています。そしてrender()
メソッド内では、コンテキスト名.Provider
という形式でProviderを定義しています。Providerは、他所からアクセスさせたい値をvalue
propにセットします。
次はBコンポーネントです。ただのステートレスな中間コンポーネントです。
import React from "react";
import C from "./C";
const B = () => {
return (
<div>
<C />
</div>
);
};
export default B;
最後にCコンポーネントです。
import React from "react";
import { CountContext } from "./A";
const C = props => (
<CountContext.Consumer>
{context => {
return (
<div>
<h2>{context.count}</h2>
</div>
);
}}
</CountContext.Consumer>
);
export default C;
<コンテキスト名.Consumer>
でConsumerを配置します。ラップされている内部の関数のcontext引数には、コンテキストの値が格納されています。ここではAコンポーネントのstate値です。あとはcontext.count
でcount値を出力しているだけです。propsを経由しなくても親コンポーネントの値にアクセスできました!
メソッド呼び出す
Consumer内から参照している値を更新することも可能です。Reduxのようにアクションをディスパッチする必要はなく、単にメソッドを呼び出すだけです。
ここでは先ほどの例を使い、シンプルなカウンターを作成してみましょう。
まず、Aコンポーネントにcount値を増減する2つのメソッドを定義します。
onIncrement = (e) => {
this.setState((prevState) => ({
count: prevState.count + 1
}));
}
onDecrement = (e) => {
this.setState((prevState) => ({
count: prevState.count - 1
}));
そしてstate内に、上記2つのメソッドをコールする関数を定義します。メソッドは2つともactionsというプロパティ名を持つオブジェクト内に定義します。
state = {
count: 0,
actions: {
onIncrement: () => this.onIncrement(),
onDecrement: () => this.onDecrement()
}
};
最後にConsumer内に、このメソッドを呼び出すボタンを作成します。
<button onClick={context.actions.onIncrement}>+</button>
<button onClick={context.actions.onDecrement}>-</button>
仕組みは値の場合とまったく同じです。Providerのvalue
propにAコンポーネントの状態をセットしているため、context.actions
経由でメソッドを呼び出すことができるのです。
勿論、Aコンポーネント内から自身のメソッドを呼び出しても問題ありません。
// Aコンポーネントのrender()
render() {
return (
<div>
<CountContext.Provider value={this.state}>
<B />
</CountContext.Provider>
<button onClick={this.onIncrement}>add</button>
<button onClick={this.onDecrement}>dec</button>
</div>
);
}
state.counter
値の整合性を気にする必要はありません。参照先は同じです。