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
クラスの中に、todos
とfilter
という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
filter
とtodos
を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では、filter
stateを渡すだけではなく、コントロール内で状態を維持したいという場合があります。幸い、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つのことが必要です。
入力値を保持するためにstateを作る
javascript this.state = { labelInput: "" };
値を更新するためのコールバック関数を作る
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
itemCount
を利用して、現在のアイテム数を表示してください。- 三項演算子を利用し、
itemCount === 1
であるかどうかで"item"と"items"を切り替えるようにしてください。
TodoListItem
- 分割代入を利用して
label
,completed
を代入してください。 - テキストを
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> ); } }