イベントハンドラの作成と授受

  • 目標:Roomを新規追加する画面(RoomForm)を追加
  • 学習:stateを変更できるイベントハンドラを作成し、子へpropsとして渡す

概要

Roomの追加はstateの変更になるので、子からは直接書き換えはできまぜん。

「親→子の単一データフロー」を厳密に守るため、Reactでは以下の更新手法を取ります。

  1. 子から親に「変更の依頼」を通知する
  2. 親は依頼内容を元に、「自分のStateを変更」する

あとはReactが自動的に以下の処理をしてくれます。

  1. ReactはStateの変更を自動的に子へ通知する
  2. 子は自身のVDOMが変更されるならば、自動的にDOMを書き換える

実装

/assets/react/Main.js

変更の依頼を受け付けるハンドラを作成し、そのなかで自身のStateを変更します。

作成したハンドラは、子に対してpropsとして渡します。子はこのハンドラを呼び出すことで変更の依頼を発火することが出来ます。

export default class Main extends React.Component {
  /* 省略: constructor() { ... } */

  // 新しいRoomを作成し追加する
  _addRoom(roomName) {
    console.log(`addRoom: ${roomName}`);

    // stateを複製し、新しいRoom要素を追加
    const newRooms = [].concat(this.state.rooms);
    newRooms.push({
      id: newRooms.length + 1,
      name: roomName,
      messages: []
    });

    // 複製した要素で、現在のStateを差し替える。
    // 変更したStateは、すべての子要素に自動的に通知される
    this.setState({
      rooms: newRooms
    });
  }

  render() {
    // イベントハンドラを子に渡す。.bind(this) を必ず行うこと
    return ( 
      <div id="page">
        <RoomPane {...this.state}  onAddRoom={this._addRoom.bind(this)} />
      </div>
    );
  }
}

/assets/react/RoomPane.js

propsで受け取ったハンドラを使って、stateの変更の依頼を発火します。

export default class RoomPane extends React.Component {
  render () {
    return (
      <div id="sidebar">
        <h2>Rooms ({this.props.rooms.length})</h2>
        <RoomList {...this.props} />
        <RoomForm {...this.props} />
      </div>
    )
  }
}

/* 省略: RoomList { ... } */
/* 省略: Room { ... } */

class RoomForm extends React.Component {
  constructor() {
    super();
  }

  // クリックイベントを拾って、親のイベントハンドラを呼び出す
  _handleClick(e) {
    // propsで受け取ったハンドラを呼び出す。
    // 入力したテキストは、DOMNodeから拾い上げる
    this.props.onAddRoom(React.findDOMNode(this.refs.inputText).value);
  }

  // buttonのイベントと、自身のハンドラをバインド
  render () {
    return (
      <div>
        <h3>Create Room</h3>
        <input type="text" ref="inputText"></input>
        <button onClick={this._handleClick.bind(this)}>Create Room</button>
      </div>
    )
  }
}