React 入門 #2 - Component

カスタムコンポーネント

Hello worldではReact.DOM.h1を使い、HTML要素をラッピングした組込みのコンポーネントを利用しました。今回は独自のコンポーネントを作る方法について学んでいきます。

React.createClass

カスタムコンポーネントを作るにはReact.createClassを使います。

var myComponent = React.createClass({});  

React.createClass()にはコンポーネントの仕様を表すオブジェクトを渡します。このオブジェクトには少なくともReactコンポーネントを返すrenderメソッドが定義されている必要があります。最小のコンポーネントとしては以下のようになります。

var myComponent = React.createClass({  
  render: function() {
    return React.DOM.p(null, 'my component');
  }
});

作ったコンポーネントをレンダリングするには、コンポーネントのインスタンスを生成する必要があるので、例えば以下の様にします。

var myComponent = React.createClass({  
  render: function() {
    return React.DOM.p(null, 'my component');
  }
});

React.render(  
  React.createElement(myComponent),
  document.getElementById('app')
);

ただし、このサンプルではReact.DOM.pをレンダリングしているだけなので、実際はReact.render()の第一引数に直接React.DOM.pを渡しても同じ結果を得ることができます。React.DOM.*React.createElement()をラップしたものです。

React.render(  
  // React.createElement(MyComponent),
  React.DOM.span(null, 'hello component'),
  document.getElementById('app')
);

Properties

コンポーネントはプロパティを持つことができます。プロパティの値に応じて、描画を動的に変更したり、振る舞いを決定させることもできます。

var MyComponent = React.createClass({  
  render: function() {
    return React.DOM.span(null, 'Hello ' + this.props.name)
  }
});

React.render(  
  React.createElement(MyComponent, {
    name: 'foo'
  }),
  document.getElementById('app')
);

propTypes

Componentには、propTypesとしてComponentが受け入れる属性とその型を定義しておくことができます。以下の例ではMyComponentnameという文字列の属性が必須であることを定義しています。

var MyComponent = React.createClass({  
  // Componentのプロパティリストと型を定義する
  propTypes: {
    name: React.PropTypes.string.isRequired
  },
  render: function() {
    return React.DOM.span(null, 'Hello ' + this.props.name)
  }
});

Componentのインスタンス生成時にnameを指定しない場合や、数値を与えるとwarningをだすようになります。

React.render(  
  React.createElement(MyComponent, { name: 123 }),
  document.getElementById('app')
);

// ->
// Warning: Failed propType: Invalid prop `name` of type `number` supplied to `<<anonymous>>`, expected `string`
React.render(  
  React.createElement(MyComponent),
  document.getElementById('app')
);

// ->
// Warning: Failed propType: Required prop `name` was not specified in `<<anonymous>>`.

状態

ここまでのComponentは静的なもので、予め定義した文字列を表示するだけでした。JavaScriptアプリケーションを作る上で頻発する作業は、オブジェクトの状態に応じて表示が変更するというようなパターンだと思います。データの状態に応じたDOM操作こそがReactが得意とするものです。

ReactではComponentが利用するデータの状態に応じて、ComponentのUIを再構築することができるようになっています。

状態を持つComponentを作る

例として、テキストエリアの入力に応じて、入力文字数を表示するComponentを作ってみます。まずは、静的な状態のComponentを作ります。

<div id="text-counter"></div>

<script src="react/build/react.js"></script>  
<script>  
var TextCounter = React.createClass({  
  propTypes: {
    text: React.PropTypes.string
  },
  render: function() {
    return React.DOM.div(
      null,
      React.DOM.textarea(
        {
          rows: 5,
          cols: 30,
          // 要素のデフォルト値を定義する
          defaultValue: this.props.text
        }
      ),
      React.DOM.p(
        null,
        React.DOM.small(null, 'Count: ' + this.props.text.length)
      )
    )
  }
});

React.render(  
  React.createElement(TextCounter, {
    text: 'Type something'
  }),
  document.getElementById('text-counter')
);
</script>  

ひとまずTextCounter Componentのインスタンスを生成する際に、渡したtextの値の文字数をカウントできています。

これをテキストエリアの状態に応じて動的にカウントするようにしていきましょう。まずはComponentにgetInitialState()を定義し、Componentが扱うデータを定義します。

var TextCounter = React.createClass({  
  ...

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

getInitialState()で扱うデータを返すようにすると、this.state.textのようにtextプロパティにアクセスできるようになります。次にテキストエリアの内容が変更されたら特定の処理をするようにします。

var TextCounter = React.createClass({  
  ...

  _textChange: function(ev) {
    // this.state.*を更新する
    this.setState({
      text: ev.target.value
    });
  },

  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)
      )
    )
  }
});

this.propsとthis.stateの違い

Componentのrender()から自身のプロパティを参照する方法としてthis.props.*this.state.*という2つがありました。propsはComponentを外部から設定するためのI/F、stateはComponent内部で扱う際に利用するものとして考えます。そのため、基本的にthis.propsはREAD-ONLYなものとして扱います。