やったこととか思ったこと

やったことを忘れないように気が向いたら書きます

Frontend Bootcampを読む Step1-06

Microsoftが公開していたFrontend Bootcampを読み進めていくメモ

自分なりの適当な翻訳なので間違いがあるかもしれません。
また公開されたばかりで現在進行形で更新されているので僕が読んだ時とは内容が少し変わっているかもしれません。

Step 1-06 Demo: Creating a State-Driven UI

Reactのデータは、トップダウンのようにComponent階層を上から下に伝わっていきます。stateを持つComponentのみがそのstateを変更できます。UIインタラクションが起きた時、stateを持つコンポーネントはstateの変更を受け取るために、イベントハンドラをUI Componentに渡す必要があります。

"Thinking in React"のステップ3では、アプリケーションに必要な"変更可能なstateの最小セット"を考えることが必要であると書いてあります。このデモでは、最小のstateをアプリケーションに追加し、そのデータからUIを操作します。またstateを変更できるようにします。stateを変更する方法(関数とか)は、UI Componentを通してカスケードダウンします。reconciliationでstateが変化した時に、UIの何が変更されるかを把握できます。

Adding state to TodoApp

TodoAppクラスの中に、todosfilterというstateを作成します。未完了のTodoを数えるのは、completeフィールドがfalseに設定されているtodosの数を数えれば計算できるのでstateを作る必要はありません。

// TodoApp
constructor(props) {
  super(props);
  this.state = {
    todos: {
      '04': {
        label: 'Todo 4',
        completed: true
      },
      '03': {
        label: 'Todo 3',
        completed: false
      },
      '02': {
        label: 'Todo 2',
        completed: false
      },
      '01': {
        label: 'Todo 1',
        completed: false
      }
    },
    filter: 'active'
  };
}

場合によってはTodosを配列にした方が簡単になりますが、この場合、オブジェクトを利用した方が最終的には高性能になります。

Passing state through to UI

filtertodosをComponentに渡します。

// TodoApp
render() {
    const { filter, todos } = this.state;
    return (
        <div>
            <TodoHeader filter={filter} />
            <TodoList todos={todos} filter={filter} />
            <TodoFooter todos={todos} />
        </div>
    );
}

State-driver TodoList

TodoListを見ると、すでにpropsをfilterとtodosという変数に渡し、さらにfilterのstateによってフィルタ処理されたtodosの配列を返す処理もしています。これらを使って、TodoItemをレンダリングします。

todos[id]は渡されたidに一致するtodoを返し、スプレッド演算子(…)はlabel={todos[id].label} completed={todos[id].comleted}と同じです。

return (
    <ul className="todos">
        {filteredTodos.map(id => (
            <TodoListItem key={id} id={id} {...todos[id]} />
        ))}
    </ul>
);

State-driven and stateful TodoHeader

TodoHeaderでは、filterstateを渡すだけではなく、コントロール内で状態を維持したいという場合があります。幸い、Reactにとってこれは全く問題ありません。最初にstateを扱いましょう。

Conditional class names

CSSのスタイルでは、ビジュアルをクラスを追加または削除することによって適用されます。filterを利用して、条件付きでクラスを追加し、正しい装飾をすることができます。

<nav className="filter">
  <button className={filter === 'all' ? 'selected' : ''}>all</button>
  <button className={filter === 'active' ? 'selected' : ''}>active</button>
  <button className={filter === 'completed' ? 'selected' : ''}>completed</button>
</nav>

三項演算子condition ? expressionIfTrue : expressionIfFalseはReactでよく使われています。各式はclassNameやJSXのelementの文字列になる可能性があるからです。

Adding a controlled imput

Reactでは、<input>,<textarea>,<select>などのform要素は未制御の入力(UnControlled input)または制御された入力(Controlled input)として使用できます。

未制御では、現在の値をform要素が内部的に維持し、ユーザーの操作(テキストの入力、オプションの選択など)に基づいて更新します。送信時など、コードは必要な時のみに、入力から値を取得します。イメージとしてはプレーンなHTMLのformです。

制御された場合はpropsから現在の値を取得し、コールバックを利用してユーザーによって行われた変更を親Componentに送ります。入力の値は、親Componentがコールバックに応答して、入力のpropsを更新するまで変わりません。

未制御と制御のものとを区別するのは、form Componentを書くときや使う時に理解することが大切です。詳しくはこの記事を参照してください。

TodoHeader Componentのテキストフィールドを制御に変更してみましょう。制御された入力にするには、デモですでに説明された2つのことが必要です。

  1. 入力値を保持するためにstateを作る

    javascript this.state = { labelInput: "" };

  2. 値を更新するためのコールバック関数を作る

    javascript _onChange = evt => { this.setState({ labelInput: evt.target.value }); };

これらを適用することで、未制御の入力を制御された入力に更新することができます。

<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />

React Dev Toolsがインストールされている場合はそれを確認しがならLabelInputを確認してください。

Exercise

もしアプリケーションを起動していないのなら、frontend-bootcampのルートでnpm startを実行してください。結果を確認するにはday1 step6にある"exercise"をクリックしてください。

TodoFooter

  1. itemCountを利用して、現在のアイテム数を表示してください。
  2. 三項演算子を利用し、itemCount === 1であるかどうかで"item"と"items"を切り替えるようにしてください。

TodoListItem

  1. 分割代入を利用してlabel,completedを代入してください。
  2. テキストをlabelに、checked属性をcompletedにしてください。
// TodoFooter
export const TodoFooter = (props: any) => {
  const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
  return (
    <footer>
      <span>
        {itemCount} item{itemCount === 1 ? '' : 's'} left
      </span>
      <button className="submit">Clear Completed</button>
    </footer>
  );
};

// TodoListItem
export class TodoListItem extends React.Component<any, any> {
  render() {
    const { label, completed } = this.props;
    // onChangeは実行時に出るWarningを防ぐために書いてます。
    return (
      <li className="todo">
        <label>
          <input type="checkbox" checked={completed} onChange={() => undefined} /> {label}
        </label>
      </li>
    );
  }
}