🪄

【Next.js / React / TypeScript/TailwindCSS】簡単なTODOアプリ作成してみる

2023/04/28に公開

プロジェクト作成

npx create-next-app my-todo

それぞれのバージョン

私の環境

  • node:
node -v
v14.21.3
  • TypeScript:
tsc -v
Version 5.0.4
  • npm
npm -v
6.14.18

nodeプロジェクト内でインストールされたパッケージと
そのバージョンを確認できるコマンドです。

 npm list --depth=0
├── @types/node@18.16.2
├── @types/react@18.2.0
├── @types/react-dom@18.2.1
├── autoprefixer@10.4.14
├── eslint@8.39.0
├── eslint-config-next@13.3.1
├── next@13.3.1
├── postcss@8.4.23
├── react@18.2.0
├── react-dom@18.2.0
├── tailwindcss@3.3.2
└── typescript@5.0.4

今回使用するもののインストールは以下です。

  • npm
npm install
npm install typescript
  • TailwindCSS
npm install tailwindcss

localhostに接続

プロジェクト作成したら、my-todo下に移動して下記のコマンド入力してください。

npm run dev

http://localhost:3000/

で、接続確認ができると思います。

TODOアプリ完成コード

import { useState } from 'react'

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

const initialTodos: Todo[] = [
    { id: 1, text: 'Learn Next.js', done: true },
    { id: 2, text: 'Learn React', done: false },
    { id: 3, text: 'Build a React App', done: false },
]

export default function Home() {
    const [todos, setTodos] = useState<Todo[]>(initialTodos)
    const [text, setText] = useState<string>('')

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        if (!text.trim()) return
        setTodos([
            ...todos,
            { id: Date.now(), text: text, done: false }
        ])
        setText('')
    }

    const handleDelete = (id: number) => {
        setTodos(todos.filter((todo) => todo.id !== id))
    }

    const handleToggle = (id: number) => {
        setTodos(
            todos.map((todo) => {
                if (todo.id === id) {
                    return { ...todo, done: !todo.done }
                }
                return todo
            })
        )
    }

    return (
        <div className="container mx-auto my-4 max-w-md">
            <h1 className="text-xl font-bold mb-4">My Todo List</h1>
            <form onSubmit={handleSubmit} className="mb-4">
                <input
                    type="text"
                    className="border border-gray-300 rounded-md p-2 w-full"
                    value={text}
                    onChange={(e) => setText(e.target.value)}
                    placeholder="Add a new todo"
                />
            </form>
            {todos.map((todo) => (
                <div key={todo.id} className="flex items-center mb-5">
                    <input
                        type="checkbox"
                        className="mr-2"
                        checked={todo.done}
                        onChange={() => handleToggle(todo.id)}
                    />
                    <p className={`flex-1 ${todo.done ? 'line-through text-gray-400' : ''}`}>
                        {todo.text}
                    </p>
                    <button onClick={() => handleDelete(todo.id)}>&times;</button>
                </div>
            ))}
        </div>
    )
}

完成図

解説

import { useState } from 'react'

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

これはTypeScriptのコードスニペットで、ReactライブラリからuseStateフックをインポートし、Todoという型を定義しています。

Todo型は、id、text、doneの3つのプロパティを持ちます。

idは数値型、textは文字列型、doneは真偽値型です。


const initialTodos: Todo[] = [
    { id: 1, text: 'Learn Next.js', done: true },
    { id: 2, text: 'Learn React', done: false },
    { id: 3, text: 'Build a React App', done: false },
]

これはTypeScriptのコードスニペットで、initialTodosという定数を定義しています。initialTodosは、Todo型の配列であり、3つの要素が含まれています。

それぞれの要素は、id、text、doneの3つのプロパティを持ちます。

idは数値型、textは文字列型、doneは真偽値型であり、
それぞれの値は異なります。

これらの値は初期のTodoリストの状態を表しています。


export default function Home() {
    const [todos, setTodos] = useState<Todo[]>(initialTodos)
    const [text, setText] = useState<string>('')

これはTypeScriptのコードスニペットで、Homeというデフォルトエクスポートされた関数を定義しています。この関数はReactコンポーネントとして機能し、Todoリストとテキスト入力をレンダリングします。useStateフックを使用して、todosとtextという2つの状態を管理しています。

todosはTodo型の配列であり、初期値としてinitialTodosを持ちます。

setTodosはtodosの状態を更新するために使用される関数です。

textは文字列型の状態であり、初期値として空の文字列を持ちます。

setTextはtextの状態を更新するために使用される関数です。

これらの状態は、後でTodoリストに新しいアイテムを追加するために使用されます。


    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        if (!text.trim()) return
        setTodos([
            ...todos,
            { id: Date.now(), text: text, done: false }
        ])
        setText('')
    }

これはTypeScriptのコードスニペットで、handleSubmitという関数を定義しています。

この関数は、フォームがサブミットされたときに呼び出されます。

eはReactのFormEvent型であり、HTMLフォーム要素を表します。preventDefault()メソッドは、デフォルトのフォーム送信動作をキャンセルします。

次に、テキスト入力フィールドに入力されたテキストが空でないことを確認します。

もし空であれば、関数を終了します。

それ以外の場合、setTodos関数を使用して、新しいTodoアイテムを現在のTodoリストに追加します。

追加するアイテムには、現在のタイムスタンプをidに設定し、textには入力されたテキストを、doneにはfalseを設定します。

最後に、テキスト入力フィールドの値を空に設定します。これにより、フォームが再び送信されたときにテキスト入力フィールドが空になります。


    const handleDelete = (id: number) => {
        setTodos(todos.filter((todo) => todo.id !== id))
    }

これはTypeScriptのコードスニペットで、handleDeleteという関数を定義しています。

この関数は、削除ボタンがクリックされたときに呼び出されます。

idは数値型の引数であり、削除するTodoアイテムのIDを表します。

setTodos関数を使用して、todos配列から対象のアイテムを除外します。

filterメソッドは、配列から条件に合致する要素だけを取り出して新しい配列を作成するために使用されます。

この場合、条件は、todo.idが引数で渡されたidと異なることです。

つまり、引数で渡されたIDを持つアイテムは除外され、新しいTodoリストはそれ以外のアイテムだけで構成されます。


    const handleToggle = (id: number) => {
        setTodos(
            todos.map((todo) => {
                if (todo.id === id) {
                    return { ...todo, done: !todo.done }
                }
                return todo
            })
        )
    }

これはTypeScriptのコードスニペットで、handleToggleという関数を定義しています。

この関数は、Todoアイテムの完了/未完了をトグルするために使用されます。

idは数値型の引数であり、対象のTodoアイテムのIDを表します。

setTodos関数を使用して、todos配列を新しいTodoリストで更新します。

mapメソッドは、配列の各要素に対して関数を適用し、新しい配列を作成するために使用されます。この場合、各Todoアイテムに関数が適用され、新しい配列が作成されます。

ifステートメントを使用して、todo.idが引数で渡されたidと一致する場合に、対象のTodoアイテムを更新します。...todo構文は、既存のTodoアイテムのプロパティを展開し、doneプロパティを反転させます。

つまり、完了していない場合は完了済みに、完了済みの場合は未完了に設定します。最後に、元のTodoアイテムの配列を返します。mapメソッドによって、各Todoアイテムが適切に更新された新しいTodoリストが作成されます。


 return (
        <div className="container mx-auto my-4 max-w-md">
            <h1 className="text-xl font-bold mb-4">My Todo List</h1>
            <form onSubmit={handleSubmit} className="mb-4">
                <input
                    type="text"
                    className="border border-gray-300 rounded-md p-2 w-full"
                    value={text}
                    onChange={(e) => setText(e.target.value)}
                    placeholder="Add a new todo"
                />
            </form>
            {todos.map((todo) => (
                <div key={todo.id} className="flex items-center mb-5">
                    <input
                        type="checkbox"
                        className="mr-2"
                        checked={todo.done}
                        onChange={() => handleToggle(todo.id)}
                    />
                    <p className={`flex-1 ${todo.done ? 'line-through text-gray-400' : ''}`}>
                        {todo.text}
                    </p>
                    <button onClick={() => handleDelete(todo.id)}>&times;</button>
                </div>
            ))}
        </div>
    )
}

これはTypeScriptのコードスニペットで、HomeというReact関数コンポーネントを定義しています。このコンポーネントは、Todoリストの表示と編集を担当します。

todosとtextは、useStateフックを使用してそれぞれTodoリストと入力されたテキストを管理します。handleSubmit関数は、フォームが送信されたときに呼び出され、入力されたテキストを新しいTodoアイテムとして追加します。

handleDelete関数は、削除ボタンがクリックされたときに呼び出され、対象のTodoアイテムを削除します。handleToggle関数は、チェックボックスがトグルされたときに呼び出され、対象のTodoアイテムの完了/未完了状態をトグルします。

return文は、JSXを返します。<div>要素には、Todoリスト全体が含まれています。

<h1>要素は、Todoリストのタイトルを表示します。フォームは、新しいTodoアイテムを追加するために使用されます。<input>要素は、テキストの入力として構成され、onChangeハンドラーを使用して、テキストが変更されたときにtextステートを更新します。<form>要素は、送信ボタンの押下を監視し、onSubmitハンドラーを使用して、handleSubmit関数を呼び出します。

{todos.map((todo) => ( ... ))}は、todos配列の各要素に対して、
JSXを生成するために使用されます。

keyプロパティは、Reactが各要素を一意に識別するために使用されます。

各Todoアイテムは、チェックボックス、テキスト、削除ボタンの3つの要素で構成されます。チェックボックスは、Todoアイテムの完了/未完了状態を反映します。

テキストは、Todoアイテムの説明を表示します。

削除ボタンは、Todoアイテムを削除するために使用されます。

{todo.done ? 'line-through text-gray-400' : ''}の部分は、
完了済みのTodoアイテムには、テキストに取り消し線と灰色のテキスト色が適用されるようにします。

以上が、ReactとTypeScriptを使用して、Todoリストを作成するためのコードスニペットです。

Discussion