ToDoの一覧表示

ToDoページで登録されているToDoを一覧表示できるように実装します。

stateの設計

登録されているToDoを表示するために、コンポーネントでどのような状態が必要になるかを考えていきます。

Reactでは、stateを使用することで、状態の変化を表現することができます。(参考:React - Reactの流儀 ステップ 3

コンポーネントのstateになりうるのは、ユーザーからの入力や時間の経過の中で変化したり、他のstate等を使って算出可能ではないものになります。画面に表示するToDoの一覧は、ユーザーからの入力で変化していくもので、算出することもできないため、stateとします。

次に、そのstateをどのコンポーネントに配置するのかを考えていきます。(参考:React - Reactの流儀 ステップ 4

このstateを表示するためのコンポーネントは、TodoListになります。TodoItemではそれぞれのToDoの内容を表示しますが、一覧できるように表示するのはTodoListです。また、TodoFormではこのToDo一覧に追加することができるため、TodoFormでもこのstateを扱うことになります。複数のコンポーネントでstateを必要とする場合、階層構造の中で共通の親コンポーネントがstateを持つことが適しているため、ここでは、TodoBoardにこのstateを持たせることにします。

コンポーネントの実装

現時点では、ToDoの表示内容はTodoListに静的に定義していますが、これをTodoBoardでstateを使って実装します。

Reactの関数コンポーネントでは、様々な機能を実装するためにフック(Hooks)と呼ばれる様々な機能が提供されており、useStateフックを使います。(参考

useStateフックはuseStateを呼び出すことで使用できます。引数に初期値を指定し、返り値としてstateとそれを更新するための関数をペアで返します。例えば、0から始まるカウントをstateとし、ボタンをクリックするごとにstateを更新するような場合、次のように使用します。

const [count, setCount] = useState(0);

return (
  <div>
    <label>{count}</label>
    <button onClick={() => setCount(count + 1)}>カウントアップ</button>
  </div>
);

ここでは、初期値としてはまず現在の静的なデータを使用しておきます。合わせて、REST APIで取得した際にはToDoを識別するidも返却されるため、これも追加しておきます。

stateの更新は今の段階では必要ないため、stateを次のように実装します。

const [todos] = useState([
  { id: 2001, text: '洗い物をする', completed: true },
  { id: 2002, text: '洗濯物を干す', completed: false },
  { id: 2003, text: '買い物へ行く', completed: false }
]);

このstateをTodoBoardに配置し、TodoListで表示するため、TodoListでもTodoItemと同様にプロパティを定義し、TodoBoardからstateを受け取るようにする必要があります。TodoItemと同様にtypeでプロパティの型を定義して、TodoListにプロパティを追加します。

また、先ほどstateで扱うToDoデータにidを追加したため、プロパティでToDoデータを受け取っているコンポーネントの型定義に、次のようにidを追加しておきます。

type Props = {
  id: number
  text: string
  completed: boolean
}

また、state使用前はTodoListで静的データを定義していたため、TodoList内でTodoItemを固定で3つ定義していましたが、プロパティとして引数で受け取るようにしたことで、定義するTodoItemの数が動的になります。引数は配列で受け取るため、mapを使用して動的にTodoItemを生成するようにしておきます。

これらを実装すると、TodoBoardTodoListTodoItemは次のとおりになります。

src/components/TodoBoard.tsx

import React, { useState } from 'react';
import './TodoBoard.css';
import { TodoForm } from './TodoForm';
import { TodoFilter } from './TodoFilter';
import { TodoList } from './TodoList';

type Todo = {
  id: number
  text: string
  completed: boolean
}

export const TodoBoard: React.FC = () => {
  const [todos] = useState<Todo[]>([
    { id: 2001, text: '洗い物をする', completed: true },
    { id: 2002, text: '洗濯物を干す', completed: false },
    { id: 2003, text: '買い物へ行く', completed: false }
  ]);

  return (
    <div className="TodoBoard_content">
      <TodoForm />
      <TodoFilter />
      <TodoList todos={todos}/>
    </div>
  );
};

src/components/TodoList.tsx

import React from 'react';
import './TodoList.css';
import { TodoItem } from './TodoItem';

type Todo = {
  id: number
  text: string
  completed: boolean
}

type Props = {
  todos: Todo[]
}

export const TodoList: React.FC<Props> = ({todos}) => {
  return (
    <ul className="TodoList_list">
      {todos.map(todo =>
        <TodoItem key={todo.id} id={todo.id} text={todo.text} completed={todo.completed} />
      )}
    </ul>
  );
};

src/components/TodoItem.tsx

import React from 'react';
import './TodoItem.css';

type Props = {
  id: number
  text: string
  completed: boolean
}

export const TodoItem: React.FC<Props> = ({id, text, completed}) => {
  return (
    <li className="TodoItem_item">
      <div className="TodoItem_todo">
        <label>
          <input type="checkbox" className="TodoItem_checkbox" checked={completed} />
          <span>{text}</span>
        </label>
      </div>
      <div className="TodoItem_delete">
        <button className="TodoItem_button">x</button>
      </div>
    </li>
  );
};

なお、TodoListのJSXでTodoItemを宣言しているところで、keyプロパティを使用します。これは、コンポーネントのレンダリング要否を判定するためにReactが使うプロパティで、mapで繰り返してコンポーネントを使用したりする場合、個々のコンポーネントを識別するために一意となる値を設定しておきます。(参考:React - リストのレンダー

REST APIの呼び出しとstateの更新

次に、stateを更新するため、REST APIを呼び出すタイミングを考えます。

ページを表示したタイミングでToDo一覧を表示したいので、TodoBoardコンポーネントを最初にレンダーしたタイミングで、REST APIを呼び出し、その結果からstateを更新するようにします。

関数コンポーネントでは、このようにデータの取得や更新によりコンポーネントに影響を与えることを副作用と呼び、副作用を起こす処理を実装するためのフックとして、useEffectフックが提供されています。(参考:React - useEffect

useEffectフックはuseEffectを呼び出すことで使用します。第1引数に副作用を起こす関数と、第2引数にこの副作用が依存するstateを配列で渡します。第2引数に渡したstateが更新されると、第1引数の関数が実行されます。ここでのREST API呼び出しは他のstateに依存せず、最初のレンダー後に呼び出したいため、そのような場合には空の配列([])を渡します。(参考:React - エフェクトの依存配列を指定する(落とし穴)

useStateフックの初期値で静的データを渡していましたが、REST APIの呼び出しを想定し、まずはuseEffectフックを使用して静的データでstateを更新するように実装してみます。stateの初期値としては、空の配列を渡しておきます。

src/components/TodoBoard.tsx

import React, { useEffect, useState } from 'react';
import './TodoBoard.css';
import { TodoForm } from './TodoForm';
import { TodoFilter } from './TodoFilter';
import { TodoList } from './TodoList';

type Todo = {
  id: number
  text: string
  completed: boolean
}

export const TodoBoard: React.FC = () => {
  const [todos, setTodos] = useState<Todo[]>([]);

  useEffect(() => {
    setTodos([
      { id: 2001, text: '洗い物をする', completed: true },
      { id: 2002, text: '洗濯物を干す', completed: false },
      { id: 2003, text: '買い物へ行く', completed: false }
    ]);
  }, []);

  return (
    <div className="TodoBoard_content">
      <TodoForm />
      <TodoFilter />
      <TodoList todos={todos}/>
    </div>
  );
};

ページの表示内容を確認すると、何も変わらず表示されています。いまは静的データで設定しているだけでuseEffectの処理も瞬時に完了するため、目に見えるような影響はありません。

useEffectフックで更新する準備ができたため、次に、生成したクライアントコードを使用してREST APIを呼び出すように実装します。事前に作成したBackendServiceの関数を使用して、REST APIを呼び出します。結果はPromiseで返ってくるため、thenを呼び出してレスポンスが返ってきたタイミングでstateの更新関数を呼び出すように実装します。

import React, { useEffect, useState } from 'react';
import './TodoBoard.css';
import { TodoForm } from './TodoForm';
import { TodoFilter } from './TodoFilter';
import { TodoList } from './TodoList';
import { BackendService } from '../backend/BackendService';

type Todo = {
  id: number
  text: string
  completed: boolean
}

export const TodoBoard: React.FC = () => {
  const [todos, setTodos] = useState<Todo[]>([]);

  useEffect(() => {
    BackendService.getTodos()
      .then(response => setTodos(response));
  }, []);

  return (
    <div className="TodoBoard_content">
      <TodoForm />
      <TodoFilter />
      <TodoList todos={todos}/>
    </div>
  );
};

モックを使用した動作確認

ページを表示したら、モックサーバから取得したデータが一覧で表示されることを確認します。

確認ができたら、フロントエンドの実装は完了です。

results matching ""

    No results matching ""