React 入門 #3 - Component 2

引き続き、Componentについて学んでいきます。

外部からComponentにアクセスする

レンダリングしたComponentへの参照はReact.render()の返り値を使うことができます。

var myCounter = React.render(  
  React.createElement(TextCounter. {
    defaultValue: 'Type something'
  }),
  document.getElementById('text-counter')
);

myCounterを使って、Componentが内部的に扱っているメソッドやプロパティへアクセスできます。

// textプロパティのstateを変更する(UIもアップデートされる)
myCounter.setState({ text: 'hello from outside' });

// propsを変更する(UIは影響を受けない)
myCounter.setProps({ defaultValue: 'foo' });

// Domノードを取得する
console.log( myCounter.getDOMNode() );

var props = myCounter.props;  
var states = myCounter.state;  
console.log(props, states);  

しかし、setState()setProps()でComponentの状態を変更するのは、Componentが複雑になって来た時に、一貫性のない状態になるため良い習慣ではないとされています。そこで、Lifecycle Methodと呼ばれているメソッドをComponentに実装し、プロパティの操作を監視するようにします。

Lifecycle Methods

Componentでは、そのライフサイクル内の特定のポイントで実行されるメソッドが定義されています。

例えば、外部とのI/FであるPropsが変更されたら、内部のStateを更新するというようなことは、componentWillReceiveProps()をComponentで実装して実現します。

componentWillReceiveProps: function(props) {  
  this.setState({
    text: props.defaultValue
  });
}

以下のサンプルで、各Lifecycleメソッドが実行されるポイントを確認してみましょう。

var TextCounter = React.createClass({  
  propTypes: {
    text: React.PropTypes.string
  },

  getInitialState: function() {
    return {
      text: this.props.defaultValue
    };
  },

  _textChange: function(ev) {
    this.setState({
      text: ev.target.value
    });
  },

  componentWillMount: function() { console.log('Component Will Mount', arguments); },
  componentDidMount: function() { console.log('Component Did Mount', arguments); },
  componentWillReceiveProps: function() { console.log('Component Will Receive Props', arguments); },
  shouldComponentUpdate: function() { console.log('Should Component Update', arguments); },
  componentWillUpdate: function() { console.log('Component Will Update', arguments); },
  componentDidUpdate: function() { console.log('Component Did Update', arguments); },
  componentWillUnmount: function() { console.log('Component Will Unmount', arguments); },

  render: function() {
    return React.DOM.div(
      null,
      React.DOM.textarea({
        rows: 5,
        cols: 30,
        value: this.state.text,
        onChange: this._textChange
      }),
      React.DOM.p(
        null,
        React.DOM.small(null, 'Count: ' + this.state.text.length)
      )
    )
  },
});

var myCounter = React.render(  
  React.createElement(TextCounter, {
    defaultValue: 'Type something'
  }),
  document.getElementById('text-counter')
);

componentWillMount > componentDidMountの順で呼び出され、テキストエリアを編集するとshouldComponentUpdate > componentWillUpdate > componentDidUpdateの順で呼び出されます。

コンソールからmyCounter.setProps({ defaultValue: 'hoge' })とすると、shouldComponentUpdateの前にcomponentWillReceivePropsが呼び出されます。

shouldComponentUpdateはレンダリング(render())の直前に実行されますが、falseを返すことでレンダリングをスキップしてしまうことに注意してください。後に続くcomponentWillUpdatecomponentDidUpdateも呼びだされません。

それぞれのメソッドの役割と、コールバックに渡される引数はドキュメントにまとまっています。