【React×TypeScript】初心者向けTodoアプリ作成

# はじめに
今回は、ReactとTypeScriptで簡易的なTodoアプリを作成したのですが、`Props`や`State`などの重要な概念の理解が深まったので、共有したいと思います。

# Todoアプリ概要
アプリとしては以下の機能がある非常に簡易的なものになっております。
- Todoリスト表示
- Todo追加・登録機能
- Todo削除機能

以下、実際に作成したものの挙動になります。
![9661f60d838df33b5619832a61e9506e.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/7dcbbfdf-d449-d798-284f-93c4f9095f8f.gif)

# 実装内容
まずは最上位のコンポーネントから見ていきましょう。
### Appコンポーネント
最上位のコンポーネントのため`Todoコンポーネント`の呼び出しを行っているのみになります。
```react:App.tsx
import "./App.css";
import Todo from "./components/Todo";

const App = () => {
	return (
		<>
			<div className="app">
				<h2>Reminder</h2>
				<Todo />
			</div>
		</>
	);
};

export default App;
```

## 各コンポーネント
続いて、`componentsフォルダ`にある各部品について見ていきましょう。
### ①Todoコンポーネント
```react:Todo.tsx
import React, { useState } from "react";
import { TodoList } from "../types/TodoList";
import Form from "./Form";
import List from "./List";

/**
 * Todoリストの全機能を実装するコンポーネント
 *
 * @returns
 */
const Todo = () => {
	const todosList: TodoList[] = [
		{
			id: 1,
			content: "店予約する",
		},
		{
			id: 2,
			content: "卵買う",
		},
		{
			id: 3,
			content: "郵便出す",
		},
	];

	const [todos, setTodos] = useState<TodoList[]>(todosList);

	/**
	 * Todoの削除を行う関数
	 *
	 * @param id number
	 * @returns void
	 */
	const deleteTodo = (id: number) => {
		const newTodos: TodoList[] = todos.filter((todo) => todo.id !== id);
		setTodos(newTodos);
	};

	/**
	 * Todoの登録を行う関数
	 *
	 * @param todo {TodoList}
	 * @returns void
	 */
	const createTodo = (todo: TodoList) => setTodos([...todos, todo]);

	return (
		<>
			<List todos={todos} deleteTodo={deleteTodo} />
			<Form createTodo={createTodo} />
		</>
	);
};

export default Todo;
```
ここでTodoリストに関する機能の実装を行なっています。
上から順にポイントを説明していきます。

まず、Todoの操作については`State`で状態管理したいため`useState`で`TodoList型`を扱うようにしています。

次に各機能についてですが、最初に削除機能について説明します。
削除は一意にTodoを特定することが必要になるため、引数にidを取り、引数に渡ってきたidに該当するTodoを除いたものを`setTodos()`に設定したいので、`State`で管理しているTodoに`filter()`を使用し、削除されたTodo以外を抽出するようにしています。

次に登録機能については、配列に新しくTodoを追加する処理になるため、このような記述となっています。

ちなみに、TodoList型は`typesフォルダ`で以下のようにファイルで定義しています。
```typescript:TodoList.ts
export type TodoList = {
	id: number;
	content: string;
};
```

### ②Listコンポーネント
```react:List.tsx
import React from "react";
import { TodoList } from "../types/TodoList";

type Props = {
	todos: TodoList[];
	deleteTodo: (id: number) => void;
};

/**
 * Todo一覧を構成するコンポーネント
 *
 * @param todos {TodoList[]}
 * @param deleteTodo {(id: number) => void}
 * @returns
 */
const List = ({ todos, deleteTodo }: Props) => {
	return (
		<div>
			{todos.map((todo) => (
				<div key={todo.id}>
					<button onClick={() => deleteTodo(todo.id)}>完了</button>
					<span>{todo.content}</span>
				</div>
			))}
		</div>
	);
};

export default List;
```
Listコンポーネントですが、こちらでまずポイントとなるのは、TypeScriptならではの**Propsの型定義**を行う部分です。
これがないとPropsが渡された時に「暗黙的にanyが含まれている」というエラーが出力されます。

そのため、Propsとして渡された値を確実に定義する必要があります。

また、Listコンポーネントの引数にPropsの値を分割代入して、型付けを行うことも必要です。

JSXに関しては、Todoをリスト表示するため、`map()`を使用し、直下の子要素に`key`を設定し、contentプロパティを表示するようにしています。
また、ボタンが押された際にイベントを発火し、Todoを削除する処理を行なうようにしています。

### ③Formコンポーネント
```react:Form.tsx
import React, { FormEvent, useState } from "react";
import { TodoList } from "../types/TodoList";

type Props = {
	createTodo: (todo: TodoList) => void;
};

/**
 * Todo入力欄を構成するコンポーネント
 *
 * @param createTodo {(todo: TodoList) => void}
 * @returns
 */
const Form = ({ createTodo }: Props) => {
	const [enteredTodo, setEnteredTodo] = useState<string>("");

	/**
	 * フォーム送信時にTodoを追加するための関数
	 *
	 * @param e onSubmitイベント
	 * @returns
	 */
	const addTodo = (e: FormEvent<HTMLFormElement>) => {
		e.preventDefault();

		// 入力欄が空の場合、処理を抜ける
		if (enteredTodo === "") return;

		// 追加するTodoオブジェクトの作成
		const newTodo: TodoList = {
			id: Math.floor(Math.random() * 1e5),
			content: enteredTodo,
		};

		// Todo登録
		createTodo(newTodo);
		// 入力欄を空にする
		setEnteredTodo("");
	};

	return (
		<div>
			<form onSubmit={addTodo}>
				<input
					type="text"
					value={enteredTodo}
					onChange={(e) => setEnteredTodo(e.target.value)}
				/>
				<button>追加</button>
			</form>
		</div>
	);
};

export default Form;
```
こちらの実装では、まず入力欄で入力された値を保持するための`State`が必要になるので、これを`useState`を用いて定義します。

これを定義したら、**JSXと紐づける**必要があるので`inputタグ`の`value属性`にuseStateの参照値を値として定義します。
また、入力欄に入力された際の入力値を保持するため、`onChangeイベント`も定義してuseStateの更新関数に渡します。

これが実装できたら、実際にTodoを追加する処理を実装します。
そのために、formのイベントである`onSubmitイベント`を設定します。

このイベントですが、デフォルトでは`formタグ`の`action属性`が発動するようになっており、これが発動すると、指定したURLに画面遷移するようになっています。（何も指定しない場合は、現在の画面に遷移）
つまり、`onSubmit`が発火するとページのリロードが起きてしまいます。
そのため`e.preventDefault()`でページのデフォルト動作を無効化してあげる必要があります。

あとは、実際にTodoを登録する処理の実装になるのですが、これは以下の手順で簡単に実装できます。
①追加するTodoListオブジェクトを作成する
②Propsとして渡された`createTodo()`を実行する

これでTodo追加・登録処理は完了です。

# さいごに
Todoアプリ自体に作れるものですが、Reactのみの場合とTypeScriptをテンプレートに使用した場合では、Propsの実装等で難易度が上がるかと思います。

また、基礎的な内容ではありますが、Reactの重要な概念を学ぶには非常に学ぶ点が多いので、初学者の方など参考にしていただけると幸いです。

# 参考文献
- [TypeScriptでコンポーネントにPropsで関数を渡す方法（Props）](https://reigle.info/entry/2022/10/10/100000)
- [any型で諦めない React.EventCallback](https://qiita.com/Takepepe/items/f1ba99a7ca7e66290f24)