React


コンポーネントの合成


6.1 HTML の拡張

React では, React.createClass API を使用してコンポーネントを作ります。

小さくて単純なコンポーネントを組み合わせて複雑で大きなアプリケーションを作成していきます。

6.2 合成の例

多肢選択問題を表示するコンポーネント

HTML には単一の選択肢を表現する基本要素がすでに定義されている

ラジをボタン (type="radio"のinput要素)

<MultipleChoice> -> <RadioInput> -> <input type="radio">

6.2.1 HTML の組み立て

React には input 要素に相当する定義済みコンポーネントを提供している

React.Dom.Input ネームスペース

input コンポ―ンとを RadioInput コンポーネントでラップします。 RadioInputコンポーネントの役割は、inputコンポーネントの機能をラジをボタンに限定することです。

枠組みを

var AnswerRadioInput = React.createClass({
  render: function(){
    return (
      <div className='radio'>
        <label>
          <input type="radio">
          ラベルの文字列
          />
        </label>
      </div>
    );
  }
});

6.2.2 動的なプロパティの追加

親コンポーネントから渡されるプロパティを定義する

var AnswerRadioInput = React.createClass({
  propTypes: {
    id:React.PropTypes.string,
    name:React.PropTypes.string.isRequired,
    label:React.PropTypes.string.isRequired,
    value:React.PropTypes.string.isRequired,
    checked:React.PropTypes.bool
  },
  ...
  });

オプションののものは初期値が必要なので getDefaultProps で指定します

var AnswerRadioInput = React.createClass({
  propTypes: {...},
  getDegaultProps: function(){
    return  {
      id: null,
      checked: false
    };
  },
  ...
  });

6.2.3 state の監視

時間とともに変化するデータを監視する場合、コンポーネントのstateに保持されます。

id は、時間とともに変化しませんが、インスタンスごとにユニークなのでstateとして保持します

checked は、ユーザーのラジオボタン操作により変更されるのでstateとして保持します

state の初期値は

var AnswerRadioInput = React.createClass({
  propTypes: {...},
  getDegaultProps: function(){...},
  getInitialState: function(){
    return {
      checked:!!this.props.checked,
      id: this.props.id ? this.props.id : uniqueId('radio')
    };
  },
  ...
  });

render メソッド内にこれらの値を参照するようにします

var AnswerRadioInput = React.createClass({
  propTypes: {...},
  getDegaultProps: function(){...},
  getInitialState: function(){...},
  render: function(){
    return (
      <div className='radio'>
        <label htmlFor={this.state.id}>
          <input type="radio">
            name={this.state.name}
            id={this.state.id}
            value={this.state.value}
            checked={this.state.checked}
          />
          {this.props.label}
        </label>
      </div>
    );
  }
});

6.2.4 親コンポーネントへの結合

このコンポーネントの役割は、複数の選択肢をユーザーに提示することで

var AnswerMultipleChoiceQuestion = React.createClass({
  propTypes: {
    value:React.PropTypes.string,
    choices:React.PropTypes.arrays.isRequired,
    onCompleted:React.PropTypes.func.isRequired
  },
  getInitialState: function(){
    return {
      id:uniqueId('multiple-choice-'),
      value:this.props.id 
    };
  },
  render: function(){
    retun (
      <div className="form-group">
        <label className="survey-item-label" htmlFor={this.state.id}>
          {this.props.label}
        </label>
        <div className="survey-item-content">
          <AnswerRadioInput ...>
            ...
          <AnswerRadioInput ...>
        </div>
</div> ); } });

var AnswerMultipleChoiceQuestion = React.createClass({
  ....
  renderChoices: function(){
    return this.props.choices.map(function(choice, i){
      return <AnswerRadioInput
        key={"choice-" + i}
        name={this.state.id}
        label={choice}
        value={choice}
        checked={this.state.value == choice} />
    }.bind(this));
  },
  render: function(){
    retun (
      <div className="form-group">
        <label className="survey-item-label" htmlFor={this.state.id}>
          {this.props.label}
        </label>
        <div className="survey-item-content">
          {this.renderChoices()}
        </div>  
      </div>
    );
  }
});

このコンポーネントを表示するには

<AnswerMultipleChoiceQuestion choices={arrayOfChoices} ... />

6.3 親子間の関係

この時点でフォームを表示することはできるが、 AnswerRadioInputは親コンポーネントと通信していないため ユーザーの入力を共有できません。

子コンポーネントが親コンポーネントと通信する簡単な方法は propsを使用することです。 親がコールバック関数をpropsとして渡し、子がそれを呼び出します。

handleChanged メソッドを定義して、
子コンポーネントの onChanged プロパティとして渡しています

var AnswerMultipleChoiceQuestion = React.createClass({
  ....
  handleChanged: function(value){
    this.setState({value:value});
    this.props.onCompleted(value);
  },
  renderChoices: function(){
    return this.props.choices.map(function(choice, i){
      return AnswerRadioInput({
        ...
        onChanged: this.handleChanged
      });  
    }.bind(this));
  },
  ...
});

これで各ラジオボタンがユーザーの入力を親に通知する準備が整いました。

あとは、このイベントハンドラを input要素の onChenge メソッドに接続します

var AnswerRadioInput = React.createClass({
  propTypes: {
    ...
    onChenged: Reaact.PropTypes.func.isRequired
  },
  handleChanged: function(e){
    var checked = e.target.checked;
    this.setState({checked:checked}):
    if (checked){
      this.props.onChanged(this.props.value);
    }
  },
  getDegaultProps: function(){...},
  getInitialState: function(){...},
  render: function(){
    return (
      <div className='radio'>
        <label htmlFor={this.state.id}>
          <input type="radio">
            ...
            onChange={this.handleChanged}
          />
          {this.props.label}
        </label>
      </div>
    );
  }
});