React:ToDo アプリを作ってみる

モックから考える

React の流儀に従い、まずはモックから構成を考える。

Vue のプラクティスで作った ToDo アプリを React でも作ろうと思います。

モックは下のような感じ。

image.png

LocalStorage のデータは次のように管理する。

{ maxId:Todo の連番, todoItems: [{"id","text":"hoge","editingText":"hoge","isEditing":"false"},{"id","text":"foo","editingText":"foo","isEditing":"false"}] }

Step1: UI をコンポーネントの階層構造に落とし込む

単一責務の原則は『一つのコンポーネントには理想的には一つのことをだけをするべき』ということで、Step1ではそうするために、小さなコンポーエントに分割する。

UIとデータモデルは同じ情報構造を持つ傾向があるため、UI を分割して、それぞれのコンポーネントがデータモデルの一つだけを表現するようにする。

image.png

No. コンポーネント 役割
ToDoApp ToDo アプリ全体
AddTodo ToDo 入力の受付
ToDoTable ToDo の集合を表示
TodoRow 各ToDo を1行で表示

次に階層構造で表現する。

  • ToDoApp
    • AddTodo
    • ToDoTable
      • ToDoRow

Step2: React で静的な ToDo アプリを作成する

props は親から子へデータを渡すための手段。 state はユーザ操作や時間経過などで動的に変化するデータを扱うために確保されている機能。よって静的なバージョンでは用いる必要はない。

コンポーネントトップダウンでもボトムアップでも問題ない。一般的にはシンプルなアプリではトップダウンに作った方が楽で、大きなプロジェクトでは開発しながらテストを書き、ボトムアップに進める方が楽だと言われている。

今回はそんなに大きくないアプリだと思うので、ボトムアップに作っていく。

静的なリストで表示してみる

静的なデータを用いて、props でデータをやり取りして、ToDo 一覧を表示してみた。

image.png

<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 項目 コンポーネント
新規登録用フォーム AddTodo
編集用フォーム TodoRow

とりあえず、新規登録用のフォームを用意してみました。

image.png

Step5: 逆方向のデータフローを追加する

上記までは props と state の情報が階層構造に従い流れ落ちていくのみだった。次にやることは階層構造の奥深くのフォームコンポーネントから上の階層にある state を更新できるようにする。

コンポーネントの state を更新できるのは自分自身のみにべきなので、上の階層は下の階層に対して、上の階層の state 更新用のコールバックを下の階層に渡しておく。下の階層では更新したいときにコールバックを呼べばOK。

フォーム入力については onChange イベントを利用すれば入力を受け付けることができる。試しに作ってみたのが下のもの。

Image from Gyazo

参考