React の Context を使って Flux を実装する

  • 72
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

何の目的で使うか

ある親の要素以下では、子供はある特定の親(おそらくは根)に依存した特定の this.context の状態を持てる機能がContextである。

Reactでは要素の受け渡しにひたすら子へリレーする方法がメジャーだが、mixinで必要なプロパティはユーザーからは隠したい事が多い。そこでcontextを使う。

注意点として、これはReactのフレームワーク実装者やmixin提供者に必要な知識であって、一般的なReactアプリケーションの中でContextを使うのは複雑すぎるので個人的には推奨しない。Reactとしてもundocumetedである。

参考: React.js: Communication between Components with ContextsJScrambler Blog

概要

  1. 親で static childContextTypes = {...} と getChildContext() を実装する
  2. 子で static contextTypes = {...} を実装する
  3. 子で this.context は getChildContext の結果を参照できる

最小コード例

import React from 'react';
import Dom from 'react-dom/server';

class Child extends React.Component {
  static get contextTypes() {
    return {a: React.PropTypes.any};
  }

  render() {
    return <span>{this.context.a}</span>;
  }
}

class Parent extends React.Component {
  static get childContextTypes() {
    return {a: React.PropTypes.any};
  }

  getChildContext() {
    return {a: 1};
  }

  render() {
    return <Child/>;
  }
}

const ret = Dom.renderToString(<Parent/>);
console.log(ret); // <span data-reactid=".1rrwza5woao" data-react-checksum="1038880381">1</span>

a = 1 としてちゃんと描画されている。

注意点として、子と親の contextTypes は直感に反して「型を明記するのにあると便利」ではなく「必須」である。ここがpropTypesと大きく異なる。

Flux を実装する

ある親以下の要素の以下では同一のEventEmitter への参照を持ちたい

Ardaでやってる dispatch mixin と同等の実装

// context
import React from 'react';
import Dom from 'react-dom';
import {EventEmitter} from 'events';

const SharedTypes = {emitter: React.PropTypes.any};

class Provider extends React.Component {
  static get childContextTypes() {
    return SharedTypes;
  }

  getChildContext() {
    return {emitter: this.props.emitter}; 
  } 
}

class DispatchableComponent extends React.Component {
  static get contextTypes() {
    return SharedTypes;
  }
  dispatch(...args) {
    return this.context.emitter.emit(...args);
  }
}

// Usage
class App extends Provider {
  render() {
    return (<Content {...this.props.childState} />);
  }
}

class Content extends DispatchableComponent {
  render() {
    return (
      <span
        onClick={() => this.dispatch('foo')}
      >
      {this.props.count}
      </span>
    );
  }
}

const emitter = new EventEmitter();
function render(state) {
  Dom.render(<App emitter={emitter} childState={state}/>, document.body);
}

// flux loop の Dispatcher 相当
const state = {count: 1};
emitter.on('foo', () => {
  // 何か操作する
  state.count += 1;
  // 再描画
  render(state);
});
render(state);

Emitterとdispatchの関係、dispatchの実装の中身、ReactとEmitterの関係を隠蔽できた。後は state と emitter のロジックを組み立てる実装、stateとReactElementの関係を別に記述して開発すれば良い。