- モックから考える
- Step1: UI をコンポーネントの階層構造に落とし込む
- Step2: React で静的な ToDo アプリを作成する
- Step3: 必要十分な state の決定
- Step4: state をどこに配置するべきなのかを明確にする
- Step5: 逆方向のデータフローを追加する
- 参考
モックから考える
React の流儀に従い、まずはモックから構成を考える。
Vue のプラクティスで作った ToDo アプリを React でも作ろうと思います。
モックは下のような感じ。
LocalStorage のデータは次のように管理する。
{ maxId:Todo の連番, todoItems: [{"id","text":"hoge","editingText":"hoge","isEditing":"false"},{"id","text":"foo","editingText":"foo","isEditing":"false"}] }
Step1: UI をコンポーネントの階層構造に落とし込む
単一責務の原則は『一つのコンポーネントには理想的には一つのことをだけをするべき』ということで、Step1ではそうするために、小さなコンポーエントに分割する。
UIとデータモデルは同じ情報構造を持つ傾向があるため、UI を分割して、それぞれのコンポーネントがデータモデルの一つだけを表現するようにする。
No. | コンポーネント名 | 役割 |
---|---|---|
① | ToDoApp | ToDo アプリ全体 |
② | AddTodo | ToDo 入力の受付 |
③ | ToDoTable | ToDo の集合を表示 |
④ | TodoRow | 各ToDo を1行で表示 |
次に階層構造で表現する。
- ToDoApp
- AddTodo
- ToDoTable
- ToDoRow
Step2: React で静的な ToDo アプリを作成する
props
は親から子へデータを渡すための手段。 state
はユーザ操作や時間経過などで動的に変化するデータを扱うために確保されている機能。よって静的なバージョンでは用いる必要はない。
コンポーネントはトップダウンでもボトムアップでも問題ない。一般的にはシンプルなアプリではトップダウンに作った方が楽で、大きなプロジェクトでは開発しながらテストを書き、ボトムアップに進める方が楽だと言われている。
今回はそんなに大きくないアプリだと思うので、ボトムアップに作っていく。
静的なリストで表示してみる
静的なデータを用いて、props
でデータをやり取りして、ToDo 一覧を表示してみた。
<body> <div id="todoList"></div> ... </body>
function ToDoApp(props) { return ( <div className="ToDoApp"> <h1>ToDoアプリ</h1> <h2>ToDo追加</h2> <div className="AddTodo"></div> <h2>ToDo一覧</h2> <ul>{props.todoItems}</ul> <div className="TodoTable"> <div className="ToDoRaw"></div> </div> </div> ); } const listItems = ["hoge","foo"].map((todo) => <li>{todo}</li> ); const todoApp = ReactDOM.createRoot(document.getElementById("todoList")); todoApp.render(<ToDoApp todoItems={listItems} />);
Step3: 必要十分な state の決定
上の内容は静的データで ToDo アプリを構築した。それには props を用いていたが、props は読専データを扱うものなので、インタラクティブな UI にするためには元となっているデータモデルを更新できる必要がある。そこで用いるのが state。
適切に開発を進めるには、アプリに求められている更新可能な状態の最小構成を検討しておくと良いらしく、『DRY の原則』が重要になるとのこと。
ToDo アプリを形作るデータは次のもの。
- 新規登録用フォーム
- ToDo リスト
- 編集用フォーム
state になりうるかは次の質問を考える。
- 親から props を通じて与えられるデータなら state ではない
- 時間経過で変化しないデータなら state ではない
- コンポーネント内の他の props や state から算出可能なデータなら state でない
新規登録用フォームは時間変化するので state っぽい。ToDo リストは一覧自体は LocalStorage で管理すると思うので props で渡す気がするので state ではない?編集用フォームも既存の ToDo リストの表示内容をデフォルト入力として表示するものの時間変化するものだから state っぽい。
というわけで、次のような構成だと判断して進める。
項目 | state 判断 |
---|---|
新規登録用フォーム | state である |
ToDo リスト | state ではない |
編集用フォーム | state である |
Step4: state をどこに配置するべきなのかを明確にする
state の最小構成を明確にしたら、次は どのコンポーネントが state を変化させるか(state を所有するか)を明確にする。
アプリに対して次のことを確認していく。
- 当該 state を使って表示を行う、すべてのコンポーネントを確認する
- 共通の親コンポーネントを見つける(階層構造の中で、当該 state を必要としているすべてのコンポーネントの上位にある単一コンポーネント)
- 共通の親か、その階層構造でさらに上位の別のコンポーネントが state を持つべき
- state を持つにふさわしいコンポーネントがいない場合、state を保持するためだけの新しいコンポーネントを作り、当該 state を必要とするコンポーネントの共通の親となる位置に配置する
state 項目 | コンポーネント名 |
---|---|
新規登録用フォーム | AddTodo |
編集用フォーム | TodoRow |
とりあえず、新規登録用のフォームを用意してみました。
Step5: 逆方向のデータフローを追加する
上記までは props と state の情報が階層構造に従い流れ落ちていくのみだった。次にやることは階層構造の奥深くのフォームコンポーネントから上の階層にある state を更新できるようにする。
コンポーネントの state を更新できるのは自分自身のみにべきなので、上の階層は下の階層に対して、上の階層の state 更新用のコールバックを下の階層に渡しておく。下の階層では更新したいときにコールバックを呼べばOK。
フォーム入力については onChange
イベントを利用すれば入力を受け付けることができる。試しに作ってみたのが下のもの。