JavaScriptのライブラリ、Reactの試し書き。Reactを使用したWebアプリの実装手順を整理したいため、React公式のdocumentation, Thinking in Reactを参考にしながら、TODOアプリを作成してみる。

Start with a mock

TODOアプリのmockを作成しておく。TODOリストと新規TODO追加用のフォーム、また状態毎のTODOを表示するためのセレクタを配置してみた。デザイナーでは無いのでそんなに見た目イケイケなものは作れない。

Mock of todo app

Step 1: Break the UI into a component hierarchy

設計したUIをプログラムに落とし込む前準備。

  • UIをcomponentが階層上に構成されたものとみなし、それぞれのcomponentに名前をつける。上でmockを作っているならそのレイヤー名なんかを使えばよろし。
  • コンポーネントの分け方は通常のプログラミング同様、単一責任の原則(signle responsibility principle)に従うべき。
  • ModelとUIは同様の構造を持つようになるだろうし、そうであるべき。

今回のtodo appでは以下のようにcomponentを定義し作成していく。

Component hierarchy of todo app

Step 2: Build a static version in React

Reactでstatic versionのUIを作る。

  • static versionということは表示に使用するデータをAPI等外部とのやりとりではなく、プログラム上やファイルにベタ書きしたものを使ってまずは、ということ。
  • staticという前提上、このステップでReactのcomponentを作る際には、propを使いstateは使わない

Step 3: Identify the minimal (but complete) representation of UI state

Step 2で作ったstatic UIをインタラクティブなものにするために、何をstateにするかを判断する。

  • 必要最小限なものを選ぶ。以下のようなデータはstateとはみなさない。
    • 親componentからpropで渡されるもの
    • 時間に依らず不変なもの
    • 他のpropstateから導けるもの (e.g. TODOの個数)

以上を踏まえて、今回のTODOアプリでは以下のものをstateとする。

  • 新規TODO入力フォームの内容
  • TODOリスト
  • 表示するTODOの種類を決めるためのセレクタ

Step 4: Identify where your state should live

Step 3で定めたstateをどのcomponentに持たせるかを決める。決め方は

  1. stateを描画するcomponentがどれかを判断する。
  2. 1.で見つけたcomponentに共通の親componentを探す。
    1. のcomponentあるいは更にその親となるようなcomponentにstateを持たせる。

という感じ。もし3.に当てはまるようなcomponentが無い場合は、新規に親componentを作ってしまえば良い。

今回はTodoBoxstateを所有させるのが適当そう。

Step 5: Add inverse data flow

Step 4の状態ではstateを表示することはできるが、画面からstateを変更することができない(変更が反映されない)。なのでonChange等のイベントハンドリングを実装して双方向のデータバインディングが行われるようにする。

作成時詰まった点

  • componentのstateを変更する時にはsetStateメソッドの使用が基本みたい
    • stateはimmutableなものとして扱うべきとのこと。
  • Todo componentで発生したイベント処理の方法。対象todoを取得したい場合にどうするべきか。
    • 今回は以下のようにTodoList componentがTodoを生成するときに、そのTodoをbindしたイベントハンドラを用意することで対応した。想定通りに機能しているが、renderメソッド中で毎回bindしているのはムダがある?
render() {
  ...
  const onToggle = this.onUserToggle.bind(this, x);
  const deleteTodo = this.deleteTodo.bind(this, x);
  return (
    <Todo
      key={x.id}
      todoId={x.id}
      title={x.title}
      completed={x.completed}
      onToggle={onToggle}
      deleteTodo={deleteTodo}
    />
  );
}

サンプル用リポジトリ