React
, TyapeScript
React + TypeScript で、Todo リストを作ります。
テンプレートリポジトリ をクローンする
リポジトリ名は todo-app とします。
VSCode で開き、ローカルホストを立ち上げる
Todo モデルの作成
行頭 +
で始まるコードを追加してください。
pages/index.tsx
モックデータ(ダミーデータ)の作成 コードを追加してください。 pages/index.tsx
モデルとは、「ユーザーの目当て」です。今回作成する Todo アプリのモデルは、Todo 単体です。なぜなら、アプリの使用中、ユーザーの関心は Todo に向かっているからです。「Todo を登録」「Todo を削除」「Todo を編集」「Todo を Done にする」など、これらのタスクを実行するとき、ユーザーの関心は Todo にあると言えます。
TypeScript では、モデルの型定義ができます。今回の実装の以下の箇所で、Todo モデルの型定義が行われています。
pages/index.tsx
ここで Todo の型は、number 型のid
プロパティ、string 型のname
プロパティ、boolean 型のisDone
プロパティをもったオブジェクトと定義されています。
オブジェクトとは、プロパティの集合です。プロパティとは名前(キー)と値(バリュー)が対になったものです。オブジェクトは、{}
(カーリーブレイス)で作成します。
今回の実装だと、モックデータとして mock0、mock1、mock2 の 3 つのオブジェクトを作成しました。
pages/index.tsx
なぜ、型を定義するのでしょうか?筆者は、型定義には 2 つのメリットがあると考えています。
今回の実装から理解できる範囲で説明します。作成したモックデータを見てください。以下の部分は、「mock0
は Todo の型に従わなければならない」ということを意味しています。このようにmock0
の型を明示したことによって、mock0
は Todo のモックデータなのだということがひと目で分かるようになっています。(逆に、: Todo
の記述がなかった場合と比較してみてください。)これが 1 つ目のメリット、コードの可読性が上がると考える理由です。
pages/index.tsx
次に、作成したモックデータを以下のように壊してみます。するとエラー文が出てくるはずです。
pages/index.tsx
エラー文
TypeScript は指定した型を破ると即座にエラーを出してくれます。型定義を利用することでコードの振る舞いを限定し、予期せぬバグを防ぐことができます。これが 2 つ目のメリット、予期せぬバグが起こるの防げると考える理由です。
配列とは、値に順序をつけて格納できるオブジェクトです。 配列に格納したそれぞれの値のことを要素、それぞれの要素の位置のことをインデックス(index)と呼びます。 インデックスは先頭の要素から 0、1、2 のように 0 からはじまる連番となります。配列は、[]
(ブラケット)を使って作成することができます。今回の実装の mockTodoList
が配列です。
pages/index.tsx
配列にマウスを当てると、その型を確認できます。mockTodoList の型を確認してみましょう。
これは、「mockTodoList
の型 は Todo 型の要素の配列型」ということです。mockTodoList の型は、コードで指定したものではなく、TypeScript の型推論によって自動的に指定されたものです。
ステート todoList を作る コードを追加してください。
pages/index.tsx
todoList の中身を表示するコードを追加してください。
pages/index.tsx
useState で生成されたステートにも型があります。ステートの型は、ステートにマウスをホバーすることで確認できます。todoList
の型を確認しましょう。
ステートの初期値 mockTodoList
から型推論によって、 Todo[]
が指定されています。
オブジェクト内のプロパティの値を取得する方法を学びましょう。例えば、idInMock0
に mock0
オブジェクトの id
の値 0 を代入する場合、以下のようになります。
map 関数とは、配列の各要素に対して指定された関数を実行し、その結果から新しい配列を作成する関数です。今回の実装を確認します。
pages/index.tsx
todoList
の各要素に対して、map
関数の引数に渡された関数 (todo) => <li key={todo.id}>{todo.name}</li>
を実行し、新しい配列を返しています。 todo
とは、todoList
の各要素のことだと考えてください。todoList
が初期値の場合、上記コードの処理結果は以下のようになります。
今回の実装で map 関数に渡されていた関数は、複数行の JSX だけを返す関数です。複数行の JSX だけを返す関数は 2 つの書き方があります。今回の実装では、省略形が採用されています。 =>
に続く括弧が異なっていることに注意してください。
todoList
)を作成今回の Todo アプリには以下の 6 つの機能を実装します。「Todo を表示する」は、前のセクションで実装済みです。
上記 6 つの機能を、todoList
を使って実装します。ここでは、どのような実装になるかの概要を見ていきます。
配列 todoList
内の要素を表示する。
配列 todoList
に、要素を追加する。追加される要素は、Todo モデルの型に従っている。
配列 todoList
から、任意の id
を持った要素を削除する。
配列 todoList
内の任意の id
を持った要素の name
を変更する。
配列 todoList
内の任意の id
を持った要素の isDone
を false
から true
に変更する。
配列 todoList
から、 isDone
が true
の要素を削除する。
このセクションでは、新しい Todo を登録するためのフォームを実装します。
入力フォームの追加
pages/index.tsx
フォームにされた内容をステート text で管理する
pages/index.tsx
先程、作成したフォームを以下のように修正してください。
pages/index.tsx
input
こちらの記事を参考に解説します。
input
の value
input
は、value
を持っています。フォームに入力された内容は value
に保存されます。
イベントとは、アプリ内のユーザーの動作、出来事を指します。必要であれば、イベントに対して、何らかの反応を返す事ができます。その際用いるのがイベントプロパティです。イベントプロパティはいろいろあります。 例えば onClick
は「ユーザーがボタンをクリックしたとき」に何らかの処理を実行します。
また、イベントプロパティには型があります。TypeScript では、以下のように表現されます。使い方は後述します。
onChage
onChage
はイベントプロパティの一つです。今回の実装では、「ユーザーがフォームに入力するとき」、つまりinput
の value
に変更が起こったときに、onChange に渡された関数が実行されます。このとき実行される関数の引数には、イベントオブジェクトが渡されます。イベントオブジェクトから、イベントの様々な情報を得ることができます。
具体例を見ます。下のコードをコンポーネントの return
以下に追加してください。ローカルホストで開発者ツールの Console を開き、作成したフォームに入力します。Console に入力中の内容が表示されるはずです。
pages/index.tsx
一連の流れを解説します。
input
の value
が変化する(e) => console.log(e.target.value)
が呼び出される。今回の実装だと、onChange から渡されるイベントオブジェクトは e
と呼ばれています。 e.target.value
で フォームに入力された内容、つまりinput
の value
を取得することができます。
Home コンポーネント内のreturn
より上の箇所で、以下のコードを追加してください。
pages/index.tsx
先程のコードを以下のように変更します。
pages/index.tsx
開発者ツールの Console を確認してみてください。 フォームの入力内容にあわせて demo
が変更されていますね。これで、フォームの入力内容をステートとして管理することができました。一連の流れを説明します。
input
の value
が変化するonChange
に渡された関数が呼び出されるdemo
が更新される今回の実装を観察します。先程までの解説とほとんど同じですが、onChange
に直接関数を書いて渡すのではなく、handleChangeInput
という関数名を渡しています。onChange
に渡される引数の型が e: React.ChangeEvent<HTMLInputElement>
と指定されているところも確認してください。
pages/index.tsx
pages/index.tsx
最後に、デモで使用したコードを次に進んでください。
pages/index.tsx
pages/index.tsx
このセクションでは、フォームの入力内容をもとに新しい Todo を作成できるようにします。
登録ボタンの追加
pages/index.tsx
以下のコードを追加してください
pages/index.tsx
入力内容を Todo に登録する
pages/index.tsx
先程作成したボタンを以下のように修正してください。
pages/index.tsx
const
const
とは、再代入できない変数の宣言とその変数が参照する値(初期値)を定義できます。
再代入しようとするとエラーがでます。
let
let
とは、値の再代入が可能な変数を宣言できます。使い方は const
とほぼ同じです。
今回の実装では、唯一 nextID
がlet
で宣言されています。
++
インクリメント演算子++
は、オペランド(被演算子)をインクリメント (1 を加算) して値を返します。
今回の実装を確認してみましょう。変数nextID
がインクリメントされています。
pages/index.tsx
nextId++
は、以下のコードの省略形です。 nextID
に nextID
をインクリメントした値が再代入されています。
register
内の処理を確認します。まず、todoList
に追加するnewTodo
を作成します。ポイントは 2 つです。
newTodo
は Todo モデルの型に従っているname
の値は text
pages/index.tsx
...
spread syntax ...
を使うことで、要素を展開することができます。
今回の実装では、 register
の中で使用されています。
pages/index.tsx
todoList
が初期値の場合、spread syntax では以下のように展開されます。
register
の処理を確認します。
name
に持った newTodo
を作成todoList
内の要素に newTodo
を追加した配列を、新たに todoList
にするnextId
がインクリメントid
とは、各 Todo の識別子の役割を果たしています。Todo が作られるたびに、nextId
をインクリメントさせることで、各 Todo が固有の id
を持つようにしています。今回の実装では、すでにモックデータで 0、1、2 の id
が使われていたので、 let nextId = 3
としました。最初に作られる Todo の id は 3、それ以降、Todo の id
は 1 ずつ大きくなります。
このセクションでは、Todo を削除する機能を実装します。
削除ボタンの作成
pages/index.tsx
remove 関数の用意
pages/index.tsx
pages/index.tsx
現在のコードで、map 関数のおさらいをしましょう。
pages/index.tsx
todoList
が初期状態の場合、上記コードを map を使わないで書くと、以下のようになります。各 Todo が削除ボタンを持ちます。 onClick
内の remove
は Todo の id
を引数として受け取っています。
filter 関数とは、与えられた関数によって実装されたテストに合格したすべての配列からなる新しい配列を生成します。
今回実装した remove
を確認します。remove
は、Todo の id
を引数として受け取ります。 newState
は、 todoList
から、 remove
の引数と一致する id
を持った Todo が取り除かれた配列です。
pages/index.tsx
onClick
に渡されている remove
が実行されるtodoList
から、削除したい Todo が削除されるこのセクションでは、Todo を完了にする機能を実装します。
完了チェックボックスの用意
pages/index.tsx
toggle 関数の作成
pages/index.tsx
pages/index.tsx
<input type=“checkbox”>
でチェックボックスが作れます。checked 属性を使うことで、チェックボックスの挙動をコントロールできます。
今回の実装を確認します。 isDone
は Boolean 型でした。isDone
が true
ならチェックが入り、false
なら空欄になります。
pages/index.tsx
mock0 の isDone
を false
から true
に変えて、ローカルホストを確認してみましょう。
pages/index.tsx
論理否定 (!) 演算子 (論理反転、否定) は、真値を取ると偽値になり、偽値になると真値になります。 今回の実装を確認します。todo.isDone が true のとき isDone は false になり、todo.isDone が false のとき isDone は true になります。
厳密不等価演算子 (!==) は、2 つのオペランド(被演算子)が等しくないことを検査し、true
か false
で結果を返します。
if 文を使うことで、プログラム内に条件分岐を書けます。if 文は次のような構文が基本形となります。 条件式の評価結果が true
であるならば、実行する文が実行されます。
今回の実装を確認します。togggle
の引数と todo.id
が異なる場合は、todo
がそのまま返されます。逆に、togggle
の引数と todo.id
が同じだった場合は、if 文に続く処理 return { ...todo, isDone: !todo.isDone }
に移動します。
pages/index.tsx
上記で書かれたコードは、以下のようにリファクタリングできます。
pages/index.tsx
isDone
が true
になるこのセクションでは、完了した Todo を一掃する機能を実装します。
クリアボタンの作成
pages/index.tsx
clear 関数の作成
pages/index.tsx
pages/index.tsx
このセクションでは、Todo コンポーネントを作成します。
Todo コンポーネントを作成する まず、Home コンポーネントの上に、Todo コンポーネントを作ります。
pages/index.tsx
次に、Home コンポーネントの以下の箇所(行頭が -
のコード)を切り取ります。
pages/index.tsx
切り取った部分を、Todo コンポーネントに貼り付けます。
pages/index.tsx
todo
remove
toggle
の部分にでエラーが出ていたら正解です。
エラーが出ているのは、Todo コンポーネントの中でtodo
remove
toggle
を呼び出しても、Todo コンポーネント内にこれらに対応するものがないからです。今から、親コンポーネント(Home)から子コンポーネント(Todo)へ、todo
remove
toggle
が渡せるようにします。(詳しくは解説で)
Todo コンポーネントのインターフェイスを作る TodoProps という名前のインターフェイスを作ります。
pages/index.tsx
次に、以下のように修正してください。
pages/index.tsx
return
の中も、以下のように修正してください。先程までのエラーが消えるはずです。
pages/index.tsx
Home コンポーネントの中で Todo コンポーネントを呼び出す ローカルホストで挙動を確認して下しさい。 pages/index.tsx
コンポーネントは、特別な JavaScript の関数です。特別な点は 2 つです。
スコープとは、変数の名前や関数などの参照できる範囲を決めるものです。 スコープの中で定義された変数はスコープの内側でのみ参照でき、スコープの外側からは参照できません。例えば、関数の中で定義された変数は、その関数の中でしか参照することしかできないことになっています。
今回の実装を振り返ります。実装手順 1 の終わりでエラーが出ていたのは、Home コンポーネントの変数がスコープ外である Todo コンポーネントから呼び出されていたからです。コンポーネントは関数でしたね。
コンポーネントは親から子へ Props として変数や関数、JSX などあらゆる情報をわたすことができます。今回の実装を確認します。Home コンポーネントから Todo コンポーネントを呼び出しました。このとき、Home コンポーネントが親、Todo コンポーネントが子になります。
pages/index.tsx
Props は、todo
や onRemove
、onToggle
になります。例えば、 Home コンポーネントから Todo コンポーネントの onRemove
に渡された remove
は、Todo コンポーネントの中でonRemove
として呼び出すことが可能になります。
Todo コンポーネントを確認します。削除ボタンの onClick
内の処理でonRemove
が呼び出されています。
pages/index.tsx
インターフェイスとは、Props の型の集合です。インターフェイスを定義することで、子コンポーネントが親から受け取る Props の型を定義できます。実装を確認します。TodoProps
は、「todo
は Todo」「onToggle
は number 型の引数を受け取り void を返す関数」「onRemove
は number 型の引数を受け取り void を返す関数」という Props の型定義の集合です。
pages/index.tsx
このセクションと次のセクションで、既存の Todo を編集する機能を実装します。このセクションでは、既存の Todo を編集モードに切り替えられるようにします。
編集モードを作成する
pages/index.tsx
pages/index.tsx
三項演算子を使って、UI を分岐させることができます。実装を確認します。
pages/index.tsx
このセクションでは、編集モードで入力した内容がアプリに反映されるようにします。
反映する関数を作り、Prop として Todo コンポーネントにわたす
まず、 Home コンポーネント内に edit
を作ります。
pages/index.tsx
edit
を Todo コンポーネントに渡せるように、インターフェイスを変更します。
pages/index.tsx
edit
を Todo コンポーネントの onEdit
に渡します。
pages/index.tsx
編集フォームへの入力内容が反映されるようにする
pages/index.tsx
pages/index.tsx