ToDo一覧の絞り込み

ToDoページで表示されるToDoを絞り込みできるように実装します。

stateの設計

ToDoの表示対象を絞り込むために、どのようなstateが必要になるかを考えていきます。

ボタンを押すとどれか1つが選択された状態にするため、どのボタンが選択されているかのstateは必要そうです。

他に、絞り込んだ後のToDoの一覧は、stateとして保持する必要があるかを考えます。これは、全てのToDoを保持するstateと、どのボタンが選択されているかのstateで算出可能なため、stateにはなりません。(参考:React - Reactの流儀 ステップ 3

TodoBoardのstateを使用して算出するためには、どのボタンが選択されているかのstateも、TodoFilterではなくTodoBoardまで引き上げた方が簡単そうです。

コンポーネントの実装

コンポーネントの実装では、TypeScriptの型を利用した実装にしていきます。

TodoFilterの実装

表示対象を選択するボタンを表示するTodoFilterコンポーネントを実装します。stateの設計であったとおり、現在どのボタンが押されているかはをTodoBoardにstateとして保持させます。そのため、ここではプロパティでstateと更新関数を受け取るようにします。

また、TodoBoardで管理した場合、TodoBoardでもどういったボタンがあるかを知っている必要があります。ただし、どういうボタンがあるかはTodoFilterが決めるため、TodoFilterで選択可能な種類をTodoBoardに知らせるため、型として宣言することにします。

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

export type FilterType = 'ALL' | 'INCOMPLETE' | 'COMPLETED';

type Props = {
  filterType: FilterType
  setFilterType: (filter: FilterType) => void
}

export const TodoFilter: React.FC<Props> = ({filterType, setFilterType}) => {
  return (
    <div className="TodoFilter_content">
      <button className={filterType === 'ALL' ? 'TodoFilter_buttonSelected' : 'TodoFilter_buttonUnselected'}
              disabled={filterType === 'ALL'}
              onClick={() => setFilterType('ALL')}>
        全て
      </button>
      <button className={filterType === 'INCOMPLETE' ? 'TodoFilter_buttonSelected' : 'TodoFilter_buttonUnselected'}
              disabled={filterType === 'INCOMPLETE'}
              onClick={() => setFilterType('INCOMPLETE')}>
        未完了のみ
      </button>
      <button className={filterType === 'COMPLETED' ? 'TodoFilter_buttonSelected' : 'TodoFilter_buttonUnselected'}
              disabled={filterType === 'COMPLETED'}
              onClick={() => setFilterType('COMPLETED')}>
        完了のみ
      </button>
    </div>
  );
};

このコンポーネントで扱う種類を知らせるため、次のようにFilterTypeという型を宣言し、exportしています。

export type FilterType = 'ALL' | 'INCOMPLETE' | 'COMPLETED';

これはUnion Types(共用体型)と呼ばれるTypeScriptの型で、型を|で区切ると、その型のどれか(or条件)が代入可能になります。(参考:TypeScript - Union Types

ここでは、このUnion Typesと固定の文字列を組み合わせることで、FilterType型にはこの文字列のどれかのみ代入することが可能にしています。 TodoBoardではこの型を使用することで、間違いなくTodoFilterが受け入れられる値を使用することができます。もしこれらの文字列以外を代入しようとするとコンパイルエラーになるため、TodoBoard側のミスを防いだり、FilterTypeで種類が変わって問題が発生した場合等にコンパイルエラーで検知できるようになります。

TodoBoardの実装

続いて、TodoBoardを実装します。ここでは、先ほど実装したFilterType型のstateを配置し、現在どれが選択されているかを保持するようにします。 現在保持されているFilterTypeのstateにより、TodoListにToDoデータを渡す前にフィルタ処理を行い、絞り込んだ後のToDoデータをTodoListのプロパティに渡すようにします。

import { FilterType, TodoFilter } from './TodoFilter';

...

type ShowFilter = {
  [K in FilterType]: (todo: Todo) => boolean
}

const showFilter: ShowFilter = {
  ALL: (todo) => true,
  INCOMPLETE: (todo) => !todo.completed,
  COMPLETED: (todo) => todo.completed,
};

...

export const TodoBoard: React.FC = () => {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [filterType, setFilterType] = useState<FilterType>('ALL');
...
  const showTodos = todos.filter(showFilter[filterType]);

  return (
    <div className="TodoBoard_content">
      <TodoForm addTodo={addTodo}/>
      <TodoFilter filterType={filterType} setFilterType={setFilterType} />
      <TodoList todos={showTodos} toggleTodoCompletion={toggleTodoCompletion}/>
    </div>
  );
};

ここでは、TodoFilterが定義しているFilterType型に対応したフィルタ処理を必ず宣言するように、次のような型を定義しています。

type ShowFilter = {
  [K in FilterType]: (todo: Todo) => boolean
}

これは、Mapped typesと呼ばれるTypeScriptの型で、オブジェクトのプロパティ名や値の型を制限することができます。[K in FilterType]部分はオブジェクトのプロパティ名の型を表しています。Kは型引数であり、ここではFilterTypeに含まれる文字列のどれかがプロパティ名として使用できるようにしています。: (todo: Todo) => boolean部分は、プロパティ値の型を表しています。

この型を使い、ボタンの種類に対応するフィルタ関数を定義したオブジェクトを生成しています。

const showFilter: ShowFilter = {
  ALL: (todo) => true,
  INCOMPLETE: (todo) => !todo.completed,
  COMPLETED: (todo) => todo.completed,
};

先ほどの型により、ここでのプロパティ名にはFilterTypeに代入可能な文字列のどれかである必要があり、それ以外のプロパティ名であればコンパイルエラーになります。そのため、確実にボタンの種類に対応しているフィルタ関数を宣言されるようにしています。

ここで定義したFilterTypeに対応した関数を使用するため、次のように宣言しています。現在のstateに応じたフィルタ関数が取得され、filterに適用されるようにしています。

const showTodos = todos.filter(showFilter[filterType]);

動作確認

フロントエンドのみで実装しているため、バックエンドと接続する必要はありません。フロントエンドアプリを起動し、ToDoページが表示されたら、ボタンをクリックしてToDo一覧を絞り込んで表示することができることを確認します。

確認できたら、フロントエンドの開発は完了です。

results matching ""

    No results matching ""