ReactのContextAPIを使ってみた

クリエイティブディビジョンからインプルーブメントディビジョンに異動してきました、17新卒の早津です。

最近React v16.3でContext APIがアップデートされました。その使い方や以前とどのように変わったのかをコードを交えて紹介していきたいと思います。

そもそもContextとは何かというと公式サイトでは以下のように説明されています。

reactjs.org

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を渡すことができます。

f:id:AdwaysEngineerBlog:20180420104505p:plain

従来は最上位のコンポーネントで定義しているstateactionなどを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階層など階層が深い場合には記述量が増えかなりしんどいです。 (詳細のコードは以下のリンクから確認してみてください。)

jsfiddle.net

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を受け取ることができます。

これまでのContextchildContextTypesgetChildContextcontextTypesなどを使ってバケツリレーの問題に対応していました。

また下記のようにpropsのを定義しています。

Parents.childContextTypes = {
  count: PropTypes.number,
}

この型を定義するのは任意ではなく、必須になります。 このPropTypesを定義するために別途prop-typesをインストールしなければいけません。

階層を飛び越えてpropsを渡すことが可能になりましたが、このように依存するものが多いです。

ではではReact v16.3Contextを使ってみたいと思います。 簡単な例として親→孫と横断して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を返します。

実際にブラウザで動かすとこのようになります。

f:id:AdwaysEngineerBlog:20180420104619g:plain

親のcountの値と孫のcountの値が変更しているのがわかります。
無事に親→孫とstate、actionを渡すことができました。

今回のサンプルはgithubで公開しています。
興味のある方は是非リポジトリをcloneしてみてください。

github.com

簡単ではありますが以上になります。