第49章:練習:配列のStateにアイテムを追加する (... スプレッド構文)
キーワードは ...(スプレッド構文) と 「元の配列はそのままにしておく」 です!
🎯 この章のゴール
useStateで 配列のState を持てる ✅- 入力フォームから新しいアイテムを作って、配列に 追加 できる ✅
...スプレッド構文でsetTodos([...todos, newTodo])の意味が分かる ✅
テーマはちょっとゆるく、「やりたいことリスト(WANTリスト)」アプリを作っていきます ✨
🧠 ちょっと復習:なんでスプレッド構文が必要なの?
ReactのStateには、こんなルールがありましたね 👇
「元の配列やオブジェクトを 直接書き換えない 」
やっちゃダメな例(NG😢):
todos.push(newTodo); // ← 配列を書き換えてる
setTodos(todos); // ← これはNGパターン
こうではなくて、新しい配列を作り直して から setTodos します ✅
setTodos([...todos, newTodo]); // ← 新しい配列をつくって渡す
ここで使っている ... が スプレッド構文 です 🌟
[...todos, newTodo] は、
「
todosの中身を全部コピーして、その後ろにnewTodoをくっつけた新しい配列」
という意味になります。
🛠 実践:WANTリストアプリを作ろう ✍️
🔧 Step 0: App.tsx をこの章専用にしてOK
Viteで作ったReact+TSプロジェクトを使います。
src/App.tsxを開いて、 この章のあいだだけ 下のコードにまるっと差し替えてOKです 🙆♀️
✅ 完成形のコード(まずは全体)
先に完成形をドーンと貼ります。 このあとで、少しずつ分解して見ていきます 👀
import { useState, type ChangeEvent, type FormEvent } from "react";
type Todo = {
id: number;
title: string;
};
export function App() {
const [title, setTitle] = useState("");
const [todos, setTodos] = useState<Todo[]>([]);
const handleTitleChange = (e: ChangeEvent<HTMLInputElement>) => {
setTitle(e.target.value);
};
const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const trimmed = title.trim();
if (trimmed === "") {
// から文字は追加しない
return;
}
const newTodo: Todo = {
id: Date.now(),
title: trimmed,
};
// ✨ ここがこの章の主役 ✨
setTodos((prevTodos) => [...prevTodos, newTodo]);
// 入力欄を空に戻す
setTitle("");
};
return (
<div style={{ maxWidth: 480, margin: "0 auto", padding: "1.5rem" }}>
<h1>やりたいことリスト ✨</h1>
<form onSubmit={handleAddTodo}>
<label>
やりたいこと:
<input
type="text"
value={title}
onChange={handleTitleChange}
placeholder="例:韓国カフェ巡り ☕"
style={{ width: "100%", padding: "0.5rem", marginTop: "0.25rem" }}
/>
</label>
<button
type="submit"
style={{
marginTop: "0.75rem",
padding: "0.5rem 1rem",
cursor: "pointer",
}}
>
追加する ➕
</button>
</form>
<hr style={{ margin: "1.5rem 0" }} />
{todos.length === 0 ? (
<p>まだ何もないよ〜。やりたいことを書いてみよう ✍️</p>
) : (
<ul>
{todos.map((todo) => (
<li key={todo.id} style={{ marginBottom: "0.25rem" }}>
{todo.title}
</li>
))}
</ul>
)}
</div>
);
}
ブラウザで npm run dev をして、
- テキストボックスに「やりたいこと」を入力
- 「追加する ➕」をクリック or Enter
で、下のリストにどんどん増えていけば成功です 🎉
🔍 コード分解①:型とState
✏️ Todo 型
type Todo = {
id: number;
title: string;
};
id: 各アイテムを見分けるための番号(ここではDate.now()で作る)title: 実際に画面に表示する文字列
ReactのStateには、こういう「型付き」のデータを入れておくと 安心&VS Codeがたくさん助けてくれて便利です 💻✨
📦 State の宣言
const [title, setTitle] = useState("");
const [todos, setTodos] = useState<Todo[]>([]);
title: 入力欄に今書かれている文字列todos:Todo型の配列(最初は空の配列[])
Todo[] は「Todo がずらっと並んだ配列」という意味です。
🔍 コード分解②:イベントの型
🖊 入力欄が変わったとき
const handleTitleChange = (e: ChangeEvent<HTMLInputElement>) => {
setTitle(e.target.value);
};
ChangeEvent<HTMLInputElement>は 「<input>が変わったときのイベント」の型e.target.valueで入力された文字を取り出して、titleに保存しています。
📮 フォームが送信されたとき
const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
// ...
};
<form onSubmit={handleAddTodo}>のonSubmitイベントの型がFormEvent<HTMLFormElement>です。e.preventDefault()を呼ぶことで、 本来のフォーム送信(ページリロード)を止める ことができます。
⭐ 主役:配列のStateにアイテムを追加する
いよいよこの章のメインです ✨
const newTodo: Todo = {
id: Date.now(),
title: trimmed,
};
// ✨ ここがポイント ✨
setTodos((prevTodos) => [...prevTodos, newTodo]);
💬 setTodos((prevTodos) => [...prevTodos, newTodo]) の意味
setTodosに 関数 を渡しているのがポイントです。prevTodosは「今のtodosの中身」が入ってきます。[...prevTodos, newTodo]で新しい配列を作って、Stateを上書きしています。
図にするとこんな感じ 🎨
🧊 なんで「prevパターン」を使うの?
setTodos([...todos, newTodo]);
でも動くんですが、
「更新が連続したとき」 に prev を使っておいたほうが安全です。
ReactはStateの更新をまとめて処理することがあるので、
- 常に「一番新しいState」を元に更新したい
- だから
setTodos((prev) => ...)パターンが推奨
というイメージでOKです 💡
👀 表示部分のポイント
{todos.length === 0 ? (
<p>まだ何もないよ〜。やりたいことを書いてみよう ✍️</p>
) : (
<ul>
{todos.map((todo) => (
<li key={todo.id} style={{ marginBottom: "0.25rem" }}>
{todo.title}
</li>
))}
</ul>
)}
todos.length === 0なら「まだ何もないよ〜」メッセージを表示- 1件以上あるときは
.map()で<li>を並べて表示 key={todo.id}を付けるのを忘れないようにしましょう ✅
🌈 アレンジしてみようチャレンジ
ここからは、ちょっと自分好みにいじってみる練習です 💪
チャレンジ①:先頭に追加してみる
新しいアイテムを一番上に出したいときは、こう書き換えてみましょう 👇
setTodos((prevTodos) => [newTodo, ...prevTodos]);
[新しいの, ...前の配列]にすることで、 新しいアイテムが先頭にくるリストになります。
チャレンジ②:テーマを変えてみる
例えば…
- 「行きたいカフェリスト ☕」
- 「読みたい本リスト 📚」
- 「行きたい国リスト ✈️」
- 「推しのイベント参加予定リスト 💖」
などなど、自分がワクワクするテーマに変えてみてください ✨
placeholder の文言や、<h1> のタイトルを変えるだけでも気分が変わります。
✅ この章でできるようになったことチェック!
-
Todoみたいな 配列用の型 を自分で定義できた -
useState<Todo[]>()で 配列のState を持てた -
setTodos((prev) => [...prev, newTodo])の意味が分かった -
...スプレッド構文で 「前の配列 + 新しいアイテム = 新しい配列」が作れるようになった
ここまでできていれば、 第50章の「削除・更新」もぜんぜん怖くないです 💪🔥
おつかれさま〜!次は、今つくったリストに 「削除・更新」機能をつけて、さらにパワーアップさせていきましょう 🚀