🦾

React の学習のためにTodoアプリを作ってみた その2

2024/07/06に公開

はじめに

4ヶ月ほど前に初めてTodoアプリを作成しました。(こちらの記事)当時は、Reactの公式チュートリアルを終えたばかりで、手探り状態でなんとか完成させたという感じでした。

あれから時間も経ち、色々学習を進めたのでその時の経験を踏まえ、さらに学びを深めるためにもう一度Todoアプリを作成してみましたので、作業ログを残しておきます。

使用する技術

  • React
  • Typescript
  • CSS
    • tailwind
  • Recoil

仕様

Todoに持たせる情報

  • ID
  • タイトル
  • ステータス
    の3つ

機能

  • Todoを追加できる
  • Todoを削除できる
  • Todoを編集できる
  • Todoを絞り込んで表示できる
  • Todoの数を表示する

環境設定

PCはMacで node.jsnpmはインストールしている前提で進めます。

react-create-appvite といった時短で環境構築ができるものもありますが、今回は学習が主目的のため、これらは使わずに設定を進めていきます。

各種パッケージのインストール

ReactやTypescript、Webpackなど開発に必要なパッケージをインストールしていきます。

こちらのサイトを参考にさせていただきました。

またパッケージなどの概念については過去こちらの記事でまとめました。

npm run buildでビルドし、npm starthttp://localhost:3000/を立ち上げます。ローカルで環境を立ち上げて開発を進めます。

フォーマットの設定

ルートディレクトリに .vscode フォルダを作成し、settings.jsonファイルに次のコードを記述します。

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  }
}

これで保存をした際に自動でフォーマットがかかります。

github へリポジトリを作成する

ターミナルでgit init コマンドを入力し、gitでリポジトリ管理を行います。

その際に、ルートディレクトリにgitignore ファイルを作成し、node_modulesを記述しておきましょう。node_modulesはファイル数が膨大なので、git管理対象から外します。

作業ログ

1.状態管理を使わず最小限のUIを実装する

まず、useStateなどの状態管理は使用せず、画面上のUIを実装する。最初はコンポーネント分割はせず、全て同じファイルに書いていきます。

2.useStateを使って値を動的に変えられるようにする

以下の3つを定義して、追加した時にリストにtodoが表示されるようにする

 const [todos, setTodos] = useState<Todotype[]>(mockTodos);
 const [newTodoTitle, setNewTodoTitle] = useState("");
 const [todoId, setTodoId] = useState(3);
  
  // Todo の追加フィールドの入力をする関数
  const handleAddTodoInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    setNewTodoTitle(e.target.value);
  };

  // Todoリストに 入力した Todoを追加する関数
  const handleAddTodo = () => {
    setTodos([...todos, { id: todoId, title: newTodoTitle, status: "未着手" }]);
    setTodoId(todoId + 1);
    setNewTodoTitle("");
  };

// Todoを追加するコンポーネント
function AddTodo({ addTodoInput, newTodoTitle, addTodo }: AddTodoProps) {
  return (
    <>
      <div>
        <input
          type="text"
          value={newTodoTitle}
          onChange={addTodoInput}
        />
        <button onClick={addTodo}>追加</button>
      </div>
    </>
  );
}

3.削除ボタンを押すと、todoが消える処理を実装する

 // Todoリストを削除する関数
  const handleDeleteTodo = (id: number) => {
    const filteredTodos = todos.filter((todo) => todo.id !== id);
    setTodos(filteredTodos);
  };

4.編集ボタンを押すと、編集コンポーネントが出るように実装する

 // 編集するtodoのtitleを入力する関数
  const handleEditTodoInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    setEditTodoTitle(e.target.value);
  };

  // 編集したtodoを todoリストに反映させる処理
  const handleEditTodo = (id: number) => {
    setTodos(
      // 既存のtodosをmapし、引数の id と一致した todo の title を、editTodoTitleに変更する処理
      todos.map((todo) =>
        todo.id === id ? { ...todo, title: editTodoTitle } : todo
      )
    );
    // 編集中のidをnullにする
    setEditingTodoId(null);
    // 編集中の title も 空にする
    setEditTodoTitle("");
  };

  // 編集ボタンを押下した時に、編集フォームを表示させる関数 (id と title を受け取って、editingTodoId と editTodoTitle の状態を変更する)
  const handleOpenEditForm = (id: number, title: string) => {
    setEditingTodoId(id);
    setEditTodoTitle(title);
  };

  // キャンセルボタンを押下した時に、編集フォームを閉じる関数
  const handleCloseEditForm = () => {
    setEditingTodoId(null);
  };

5.ステータスを更新できるようにする

// Todo のステータスを変更する関数
const handleEditStatus = (id: number, status: StatusType) => {
  setTodos(
    todos.map((todo) => (todo.id === id ? { ...todo, status: status } : todo))
  );
};

<select
  value={todo.status}
  onChange={(e) => editStatus(todo.id, e.target.value as StatusType)}
  style={{ marginRight: "10px" }}
>
  <option value="未着手">未着手</option>
  <option value="進行中">進行中</option>
  <option value="完了">完了</option>
</select>;

6.ステータスごとにフィルターをかけられるように実装する

type FilterTodoProps = {
  filterTodo: (status: StatusType) => void;
};
// Todoをフィルタリングするコンポーネント
function FilterTodo({ filterTodo }: FilterTodoProps) {
  return (
    <>
      <select onChange={(e) => filterTodo(e.target.value as StatusType)}>
        <option value="全て">全て</option>
        <option value="未着手">未着手</option>
        <option value="進行中">進行中</option>
        <option value="完了">完了</option>
      </select>
    </>
  );
}
// todos, filterStatus が変更するたびに、処理を走らせることで、最新のTodoリストの状態を表示する
useEffect(() => {
  if (filterStatus === "全て") {
    setFilteredTodos(todos);
  } else {
    setFilteredTodos(todos.filter((todo) => todo.status === filterStatus));
  }
}, [todos, filterStatus]);

7.tailwindでスタイルを当てたり、コンポーネントに切り出すリファクタを行う

今回勉強の一環として状態管理の一部にrecoilを使ってみましたが、これくらいの小規模アプリでしたら状態管理を使う必要はないと思います。(hooksのみで十分)

完成!

コードはこちらです

かかった時間

  • 合計:約10時間
    • 環境構築:約30分
    • 静的ぺージの作成:1.5時間
    • ロジックの実装:5時間
    • CSSのスタイル当て:1時間
    • リファクタリング(状態管理をRecoilで書いてみる作業):2時間

まとめ

前回作成した際は1週間以上かかりましたが、今回は大分スピードが上がったのと、ReactやTypescriptを使って実装ができたので、理解がとても進みました。

Discussion