クリエイティブディビジョンからインプルーブメントディビジョンに異動してきました、17新卒の早津です。
最近React v16.3
でContext APIがアップデートされました。その使い方や以前とどのように変わったのかをコードを交えて紹介していきたいと思います。
そもそもContextとは何かというと公式サイトでは以下のように説明されています。
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
ざっくり言うと親から子、子から孫といったような順番にstate(状態)をpropsを通じて渡さなくてもコンポーネントツリーを介さずにstateを渡す方法があるよーということです。
下図のように子コンポーネントを横断してstateを渡すことができます。
従来は最上位のコンポーネントで定義しているstate
やaction
などをprops
を通じて親から子と都度渡さなくてはなりません。このような場合バケツリレーが発生してしまいます。
実際にコードを書いてみました。
//親コンポーネント class Parents extends React.Component { constructor(){ super(); this.state = { count: 10 } } render(){ return( <div className="parents"> {this.state.count} <Child count={this.state.count}/> </div> ) } } //子コンポーネント const Child = (props) => ( <div className="child"> {props.count} <GrandChildren count={props.count} /> </div> ) //孫コンポーネント const GrandChildren = (props) => ( <div className="grandChildren">{props.count}</div> ) ReactDOM.render(<Parents />, document.querySelector("#app"))
親が管理しているstate(状態)を下記のように子のコンポーネントを経由して渡しています。
...省略 //子コンポーネント const Child = (props) => ( <div className="child"> {props.count} <GrandChildren number={props.count} /> </div> ) ...省略
今回は親→子→孫
と3階層でしたが、10階層など階層が深い場合には記述量が増えかなりしんどいです。
(詳細のコードは以下のリンクから確認してみてください。)
React v16.3
以前のContextを使った簡単な例です。
import React, { Component } from 'react'; import './App.css'; import PropTypes from 'prop-types'; class Parents extends Component { getChildContext() { return { count: 0, }; } render() { return ( <div className="parents"> parents <Child /> </div> ); } } Parents.childContextTypes = { count: PropTypes.number, } class Child extends Component { render() { return ( <div className="child"> Child <GrandChild /> </div> ); } } class GrandChild extends Component { render() { return ( <div className="grandChild"> GrandChild {this.context.type} </div> ); } } GrandChild.contextTypes = { count: PropTypes.count, } export default Parents;
stateを受け取りたいコンポーントでthis.context.count
と書くとルートコンポーネント(ここでいう親コンポーネント)のstateを受け取ることができます。
これまでのContextはchildContextTypes
、getChildContext
、contextTypes
などを使ってバケツリレーの問題に対応していました。
また下記のようにpropsの型を定義しています。
Parents.childContextTypes = { count: PropTypes.number, }
この型を定義するのは任意ではなく、必須になります。
このPropTypesを定義するために別途prop-types
をインストールしなければいけません。
階層を飛び越えてprops
を渡すことが可能になりましたが、このように依存するものが多いです。
ではではReact v16.3
のContextを使ってみたいと思います。
簡単な例として親→孫
と横断してstateとactionを渡してみます。
まず親、子、孫それぞれをファイル別に分けてみます。
親ファイル
import React, { createContext } from "react"; import Child from './Child'; const Context = createContext() export const { Provider, Consumer } = Context export class Parents extends React.Component { constructor(){ super(); this.state = { count: 10 } } render(){ return( <Provider value={{ state: this.state, actions: { increment:() => this.setState({ count: this.state.count + 1 }), decrement:() => this.setState({ count: this.state.count -1 }) } }} > <div className="parents"> <div>Parents</div> {this.state.count} <Child/> </div> </Provider> ) } }
<Provider>
に渡したいactionやstateをvalueの中に定義します。
子ファイル
import React from "react"; import GrandChildren from './GrandChildren'; const Child = () => ( <div className="child"> <div>Child</div> <GrandChildren/> </div> ) export default Child;
子のファイルでは単に孫のコンポーネントを読み込んでいるだけになります。
孫ファイル
import React from "react"; import { Consumer } from './Parents'; const GrandChildren = (props) => ( <Consumer> {({ state, actions }) => ( <div className="grandChildren"> <div>GrandChildren</div> <span>{state.count}</span> <div className="buttonArea"> <button onClick={actions.increment}>+</button> <button onClick={actions.decrement}>ー</button> </div> </div> )} </Consumer> ) export default GrandChildren;
<Consumer>
直下にオブジェクトを配置しその中の関数が必須になります。
この関数がstateやactionを受け取り、React Nodeを返します。
実際にブラウザで動かすとこのようになります。
親のcountの値と孫のcountの値が変更しているのがわかります。
無事に親→孫
とstate、actionを渡すことができました。
今回のサンプルはgithubで公開しています。
興味のある方は是非リポジトリをcloneしてみてください。
簡単ではありますが以上になります。