- DAY 25:配列State 前半 ― 「Stateで配列を扱う」ための土台を固める
- 配列をStateで管理するとはどういうことか
- 新しい配列を作る ― 「書き換え」ではなく「置き換え」
- Stateを直接変更しない ― なぜダメなのか
- Spread構文で「新しい配列」を自然に作る
- 練習用テンプレート:配列Stateでタスクを追加する
- DAY 25 前半のまとめ
- DAY 25:配列State 中盤 ― 「追加・削除・更新」を安全に扱うための配列思考
- 配列をStateで管理する ― 「一覧そのものがState」という感覚
- 新しい配列を作る ― 追加・削除・更新の基本パターン
- Stateを直接変更しない ― 「やってはいけない書き方」を具体的に見る
- Spread構文を「配列コピー+追加」の道具として理解する
- 練習用テンプレート:配列Stateで「追加・削除・完了切り替え」をまとめて扱う
- DAY 25 中盤のまとめ
- DAY 25:配列State 後半 ― 「実務レベルで通用する配列Stateの感覚」を仕上げる
- 配列をStateで管理する ― 「画面の一覧=配列State」という視点の定着
- 新しい配列を作る ― 「操作ごとにパターンを持つ」ことが大事です
- Stateを直接変更しない ― 「バグの温床」を具体的にイメージする
- Spread構文 ― ネストしたデータ構造にも使える「コピーの道具」
- 練習用テンプレート:ユーザー一覧+タスク一覧を配列Stateで管理する
- DAY 25 後半のまとめ
DAY 25:配列State 前半 ― 「Stateで配列を扱う」ための土台を固める
DAY 25では、Reactでとてもよく使うのに、つまずきやすいテーマである 「配列をStateで管理する」ことを丁寧に学んでいきます。
ここからのゴールは、
- 配列をStateとして持つイメージをはっきりさせること
- 「新しい配列を作る」という考え方に慣れること
- 「Stateを直接変更しない」理由を理解すること
- Spread構文(
...)を使って、自然に新しい配列を作れるようになること
です。
配列をStateで管理するとはどういうことか
「今の状態」を配列で表現する
まず、「配列をStateで管理する」という言葉を、具体的なイメージに落とし込みます。
例えば、「タスク一覧」を管理したいとします。
- タスクは複数ある
- 各タスクには「タイトル」や「完了状態」がある
このような「複数のもの」を扱うとき、 JavaScriptでは 配列 を使うのが自然です。
const tasks = [
{ id: 1, title: "Reactの勉強をする", done: false },
{ id: 2, title: "買い物に行く", done: true },
];
JSXReactでこの「タスク一覧」を画面に表示し、 さらに「追加・削除・更新」などをしたい場合、 この配列を Stateとして管理する ことになります。
import { useState } from "react";
function TodoList() {
const [tasks, setTasks] = useState([
{ id: 1, title: "Reactの勉強をする", done: false },
{ id: 2, title: "買い物に行く", done: true },
]);
return (
<div>
<h2>タスク一覧</h2>
<ul>
{tasks.map((task) => (
<li key={task.id}>
{task.done ? "✅" : "⬜️"} {task.title}
</li>
))}
</ul>
</div>
);
}
export default TodoList;
JSXここでのポイントは、
tasksが「今のタスク一覧」という 状態(State) を表していることsetTasksが「タスク一覧を更新するための関数」であること
です。
重要ポイント:
- 配列をStateにすることで、「複数のものの状態」をまとめて管理できます。
- その配列を
map()で画面に表示するのが、Reactの基本パターンです。
新しい配列を作る ― 「書き換え」ではなく「置き換え」
State更新は「新しい値に置き換える」イメージ
ReactのStateは、
「今の値を直接書き換える」のではなく、「新しい値に置き換える」
という考え方で扱います。
例えば、タスクを1件追加したいとします。
間違ったイメージはこうです。
「今の配列に直接
pushして、そのまま使う」
正しいイメージはこうです。
「今の配列を元に、新しい配列を作って、それをStateにセットする」
具体的なコードで見てみます。
const handleAddTask = () => {
const newTask = {
id: 3,
title: "メールを返信する",
done: false,
};
setTasks((prevTasks) => [...prevTasks, newTask]);
};
JSXここで起きていることは、
prevTasksに「今のタスク一覧」が入っている[...]を使って、「今のタスク一覧を展開」している- その後ろに
newTaskを追加して、「新しい配列」を作っている setTasksにその新しい配列を渡して、Stateを更新している
重要ポイント:
- State更新は「新しい配列を作って、それに置き換える」という流れで考えます。
- 「今の配列を直接書き換える」のではなく、「今の配列を材料にして、新しい配列を作る」イメージが大事です。
Stateを直接変更しない ― なぜダメなのか
「直接書き換える」とReactが変化を検知できないことがある
Reactでは、次のような書き方は NG です。
// よくある間違いの例
const handleAddTask = () => {
const newTask = {
id: 3,
title: "メールを返信する",
done: false,
};
tasks.push(newTask); // ← ここで直接配列を変更している
setTasks(tasks); // ← 同じ配列を渡している
};
JSX一見すると動きそうですが、 この書き方には問題があります。
tasks.push(newTask)で、元の配列そのものを変更しているsetTasks(tasks)で、変更済みの同じ配列を渡している
Reactは、Stateの「前回の値」と「今回の値」を比べて、 変化があったかどうかを判断します。
しかし、同じ配列オブジェクトを使い回していると、
- 「前回も今回も同じもの」と見なされてしまう
- 変化を正しく検知できないことがある
という問題が起きます。
「不変性(immutability)」という考え方
この問題を避けるために、 Reactでは 「Stateを直接変更しない」 というルールがあります。
これは、もう少し専門的な言葉で言うと、
「Stateは不変(immutable)として扱う」
という考え方です。
- 「不変」とは、「一度作ったものを直接書き換えない」という意味です。
- 書き換えたいときは、「新しいものを作って置き換える」という形にします。
重要ポイント:
- Stateを直接変更すると、Reactが変化を検知できないことがあり、 画面が正しく更新されない原因になります。
- 「Stateは不変として扱う」というルールを守ることで、 Reactの仕組みと自分のコードの考え方が揃います。
Spread構文で「新しい配列」を自然に作る
Spread構文とは何か
ここで登場するのが、JavaScriptの Spread構文 です。
const arr = [1, 2, 3];
const newArr = [...arr, 4];
// newArr は [1, 2, 3, 4]
JavaScript...arr は、
「arrの中身を、その場に展開する」
という意味です。
ReactのState更新では、このSpread構文を使って、
- 「今の配列を展開」
- 「新しい要素を追加」
という流れで、新しい配列を作ることがとても多いです。
タスク追加の例
先ほどのタスク追加の例を、Spread構文の視点からもう一度見てみます。
const handleAddTask = () => {
const newTask = {
id: 3,
title: "メールを返信する",
done: false,
};
setTasks((prevTasks) => [...prevTasks, newTask]);
};
JSXここでの [...prevTasks, newTask] は、
prevTasksの中身をすべて展開して- その後ろに
newTaskを追加して - 「新しい配列」を作っている
という意味になります。
削除や更新でもSpread構文+配列メソッドを使う
Spread構文は「追加」だけでなく、 削除や更新でもよく使われます。
例えば、「特定のタスクを完了にする」場合は、 map() と組み合わせて新しい配列を作ります。
const handleToggleDone = (id) => {
setTasks((prevTasks) =>
prevTasks.map((task) =>
task.id === id ? { ...task, done: !task.done } : task
)
);
};
JSXここでは、
prevTasks.map(...)で「新しい配列」を作り{ ...task, done: !task.done }で「オブジェクトのコピー+変更」を行っています。
{ ...task, done: !task.done } の ...task も、 オブジェクトに対するSpread構文です。
重要ポイント:
- Spread構文は、「今の配列やオブジェクトをコピーして、新しいものを作る」ための道具です。
- State更新では、「直接書き換えずに、新しいものを作る」ために、Spread構文がとてもよく使われます。
練習用テンプレート:配列Stateでタスクを追加する
最後に、DAY 25前半の内容をまとめた練習用テンプレートとして、 「配列Stateでタスクを追加する」コンポーネントを示します。
import { useState } from "react";
function TodoList() {
const [tasks, setTasks] = useState([
{ id: 1, title: "Reactの勉強をする", done: false },
{ id: 2, title: "買い物に行く", done: true },
]);
const [nextId, setNextId] = useState(3);
const handleAddTask = () => {
const newTask = {
id: nextId,
title: `新しいタスク${nextId}`,
done: false,
};
// 新しい配列を作ってStateを更新
setTasks((prevTasks) => [...prevTasks, newTask]);
setNextId((prevId) => prevId + 1);
};
return (
<div>
<h2>タスク一覧(配列State)</h2>
<button onClick={handleAddTask}>タスクを追加</button>
<ul style={{ marginTop: "8px" }}>
{tasks.map((task) => (
<li key={task.id}>
{task.done ? "✅" : "⬜️"} {task.title}
</li>
))}
</ul>
</div>
);
}
export default TodoList;
JSXこのテンプレートの中には、
- 配列をStateで管理する
- 新しい配列を作ってStateを更新する
- Stateを直接変更しない
- Spread構文で配列を展開する
というDAY 25前半の学習項目が、自然な形で含まれています。
DAY 25 前半のまとめ
DAY 25の前半では、
- 「配列をStateで管理する」とは、「複数のものの状態を配列として持ち、それをReactのStateとして扱うこと」であると整理したこと
- State更新は「今の値を直接書き換える」のではなく、「新しい値に置き換える」流れで考えることを確認したこと
- 「Stateを直接変更しない」理由として、Reactが差分を検知する仕組みと、不変性(immutability)の考え方を押さえたこと
- Spread構文(
...)を使って、「今の配列やオブジェクトをコピーし、新しいものを作る」パターンに慣れ始めたこと - タスク一覧のテンプレートを通して、 「配列State+新しい配列を作る+Stateを直接変更しない+Spread構文」 が1本の流れとしてつながる感覚を掴んだこと
が大きなポイントになります。
この段階まで来ると、
- 「配列Stateを扱うときは、必ず新しい配列を作る」
- 「Spread構文は、そのための頼れる相棒」
という感覚が、かなり自分の中に根付き始めているはずです。 中盤以降では、削除・更新・複雑な配列操作を題材に、 この考え方をさらに実践的なレベルへと広げていきます。
DAY 25:配列State 中盤 ― 「追加・削除・更新」を安全に扱うための配列思考
DAY 25の中盤では、前半で学んだ 配列をStateで管理 / 新しい配列を作る / Stateを直接変更しない / Spread構文 を、もう一歩「実用的なアプリ」に近づけていきます。
ここからのゴールは、
- 配列Stateで「追加・削除・更新」を自然に書けるようになること
- 「直接書き換えない」というルールを、実際のコードの流れとして体に馴染ませること
- Spread構文を、単なる記号ではなく「新しい配列を作るための道具」として理解すること
です。
配列をStateで管理する ― 「一覧そのものがState」という感覚
単なる「変数」ではなく「今の状態」
まず、配列をStateで管理する感覚を、もう一度確認します。
タスク一覧を例にすると、次のようなコードになります。
import { useState } from "react";
function TodoList() {
const [tasks, setTasks] = useState([
{ id: 1, title: "Reactの勉強をする", done: false },
{ id: 2, title: "買い物に行く", done: true },
]);
return (
<div>
<h2>タスク一覧</h2>
<ul>
{tasks.map((task) => (
<li key={task.id}>
{task.done ? "✅" : "⬜️"} {task.title}
</li>
))}
</ul>
</div>
);
}
export default TodoList;
JSXここでの tasks は、
- 「今この瞬間のタスク一覧」を表す 状態(State)
- 画面に表示されている内容と、1対1で対応しているもの
です。
重要ポイント:
- 配列Stateは、「画面に出ている一覧そのもの」を表していると考えると分かりやすいです。
- だからこそ、「一覧を変えたいときは、Stateの配列を変える」ことになります。
新しい配列を作る ― 追加・削除・更新の基本パターン
1. 追加:末尾に新しい要素を足す
タスクを追加する処理を考えてみます。
const handleAddTask = () => {
const newTask = {
id: Date.now(),
title: "新しいタスク",
done: false,
};
setTasks((prevTasks) => [...prevTasks, newTask]);
};
JSXここでの流れは、
newTaskに「追加したいタスク」を作るprevTasksに「今のタスク一覧」が入る[...prevTasks, newTask]で「今の一覧+新しいタスク」の配列を作る- それを
setTasksに渡して、Stateを更新する
2. 削除:特定の要素を取り除いた新しい配列を作る
次に、タスクを削除する処理です。
const handleDeleteTask = (id) => {
setTasks((prevTasks) => prevTasks.filter((task) => task.id !== id));
};
JSXここでは、
filter()を使って、「削除したいタスク以外」を残した新しい配列を作っています。prevTasksはそのまま触らず、filter()の結果として新しい配列が返ってきます。
3. 更新:特定の要素だけを書き換えた新しい配列を作る
完了状態を切り替える処理を考えてみます。
const handleToggleDone = (id) => {
setTasks((prevTasks) =>
prevTasks.map((task) =>
task.id === id ? { ...task, done: !task.done } : task
)
);
};
JSXここでは、
map()で「同じ長さの新しい配列」を作っています。- 対象のタスクだけ
{ ...task, done: !task.done }でコピー+変更しています。
重要ポイント:
- 追加 →
[...](Spread)で末尾に足す - 削除 →
filter()で「残したいものだけ」の新しい配列を作る - 更新 →
map()で「一部だけ変えた新しい配列」を作る
という3パターンが、配列Stateの基本です。
Stateを直接変更しない ― 「やってはいけない書き方」を具体的に見る
よくあるNGパターン:push や直接代入
配列Stateでよくやってしまう間違いを、あえて書いてみます。
// NG例:直接変更してしまう
const handleAddTask = () => {
const newTask = {
id: Date.now(),
title: "新しいタスク",
done: false,
};
tasks.push(newTask); // ← ここで元の配列を直接変更
setTasks(tasks); // ← 同じ配列を渡している
};
JSXこの書き方の問題は、
tasksという「今のStateの配列」を直接書き換えていることsetTasks(tasks)で「同じ配列オブジェクト」を渡していること
です。
Reactは、
- 「前回のState」と「今回のState」が違うかどうか
を見て、再レンダリングするかどうかを決めます。
しかし、同じ配列オブジェクトを使い回していると、
- 「前回も今回も同じもの」と見なされることがあり、
- 変化を正しく検知できない可能性があります。
正しいパターンとの対比
正しいパターンはこうでした。
const handleAddTask = () => {
const newTask = {
id: Date.now(),
title: "新しいタスク",
done: false,
};
setTasks((prevTasks) => [...prevTasks, newTask]);
};
JSXここでは、
prevTasksは「今の配列」ですが、直接書き換えず[...prevTasks, newTask]で「新しい配列」を作り- それを
setTasksに渡しています。
重要ポイント:
- 「Stateを直接変更しない」は、単なるお作法ではなく、 Reactが変化を正しく検知するための前提条件です。
- 「元の配列は触らず、新しい配列を作る」という癖をつけると、バグを大きく減らせます。
Spread構文を「配列コピー+追加」の道具として理解する
Spread構文の役割を言葉で説明する
Spread構文(...)は、配列Stateを扱うときに頻出しますが、 役割を一言で言うとこうなります。
「今の配列(またはオブジェクト)の中身をコピーして、新しいものを作るための記法」
例えば、
const arr = [1, 2, 3];
const newArr = [...arr, 4];
// newArr は [1, 2, 3, 4]
JavaScriptこれは、
arrの中身をコピーして- その後ろに
4を追加して - 新しい配列
newArrを作っている
という意味です。
ReactのState更新での使い方
Reactの配列Stateでは、Spread構文が次のように使われます。
setTasks((prevTasks) => [...prevTasks, newTask]);
JSXここでの [...prevTasks, newTask] は、
- 「今のタスク一覧をコピーして」
- 「新しいタスクを末尾に追加して」
- 「新しいタスク一覧を作る」
という処理です。
削除や更新では、filter() や map() がメインになりますが、 オブジェクトの更新では、オブジェクトに対するSpread構文もよく使います。
setTasks((prevTasks) =>
prevTasks.map((task) =>
task.id === id ? { ...task, done: !task.done } : task
)
);
JSXここでの { ...task, done: !task.done } は、
- 「今のtaskオブジェクトをコピーして」
- 「doneだけ反転させた新しいオブジェクトを作る」
という意味です。
重要ポイント:
- Spread構文は、「元のものを壊さずに、新しいものを作る」ための道具です。
- 配列State+不変性(直接変更しない)という考え方と、Spread構文はセットで覚えると自然になります。
練習用テンプレート:配列Stateで「追加・削除・完了切り替え」をまとめて扱う
最後に、DAY 25中盤の内容をまとめた練習用テンプレートとして、 「配列Stateで追加・削除・完了切り替え」を扱うコンポーネントを示します。
import { useState } from "react";
function TodoList() {
const [tasks, setTasks] = useState([
{ id: 1, title: "Reactの勉強をする", done: false },
{ id: 2, title: "買い物に行く", done: true },
]);
const [nextId, setNextId] = useState(3);
const handleAddTask = () => {
const newTask = {
id: nextId,
title: `新しいタスク${nextId}`,
done: false,
};
// 追加:新しい配列を作る
setTasks((prevTasks) => [...prevTasks, newTask]);
setNextId((prevId) => prevId + 1);
};
const handleDeleteTask = (id) => {
// 削除:filterで新しい配列を作る
setTasks((prevTasks) => prevTasks.filter((task) => task.id !== id));
};
const handleToggleDone = (id) => {
// 更新:map+オブジェクトSpreadで新しい配列を作る
setTasks((prevTasks) =>
prevTasks.map((task) =>
task.id === id ? { ...task, done: !task.done } : task
)
);
};
return (
<div>
<h2>タスク一覧(配列State 中盤)</h2>
<button onClick={handleAddTask}>タスクを追加</button>
<ul style={{ marginTop: "8px" }}>
{tasks.map((task) => (
<li key={task.id}>
<button onClick={() => handleToggleDone(task.id)}>
{task.done ? "✅" : "⬜️"}
</button>
<span style={{ marginLeft: "8px", marginRight: "8px" }}>
{task.title}
</span>
<button onClick={() => handleDeleteTask(task.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}
export default TodoList;
JSXこのテンプレートの中には、
- 配列をStateで管理:
tasksが「今のタスク一覧」を表すState - 新しい配列を作る:
[...]、filter()、map()で新しい配列を生成 - Stateを直接変更しない:
prevTasksをそのまま触らず、新しい配列を返している - Spread構文:配列とオブジェクトの両方に対して使っている
というDAY 25中盤の学習項目が、すべて詰まっています。
DAY 25 中盤のまとめ
DAY 25の中盤では、
- 配列Stateが「画面に出ている一覧そのもの」を表しているという感覚を、タスク一覧を通して確認したこと
- 追加・削除・更新をそれぞれ「新しい配列を作る」形で書くパターン(
[...]/filter()/map())を整理したこと - Stateを直接変更するNGパターン(
pushなど)と、正しいパターンの違いを具体的なコードで対比したこと - Spread構文を、「元のものを壊さずに、新しいものを作るための道具」として理解し、配列とオブジェクト両方で使う形を確認したこと
- 練習用テンプレートを通して、 「配列State+新しい配列を作る+Stateを直接変更しない+Spread構文」 が、追加・削除・更新を含む実用的なコンポーネントの中で自然に使われる様子をイメージできるようになったこと
が大きなポイントになります。
この段階まで来ると、
- 「配列Stateを触るときは、必ず新しい配列を作る」
- 「Spread構文と配列メソッドが、そのための基本セット」
という感覚が、かなりしっかりと自分の中に根付いているはずです。 後半では、さらに複雑なデータ構造やネストした配列・オブジェクトを題材に、 この考え方を一段深いレベルへと広げていきます。
DAY 25:配列State 後半 ― 「実務レベルで通用する配列Stateの感覚」を仕上げる
DAY 25の後半では、 配列をStateで管理 / 新しい配列を作る / Stateを直接変更しない / Spread構文 というテーマを、より「実務レベル」に近い形で仕上げていきます。
ここからのゴールは、
- 配列Stateを使って、追加・削除・更新・並び替えなどを安全に扱えるようになること
- 「Stateを直接変更しない」というルールを、バグ防止の観点から深く理解すること
- Spread構文を、ネストしたデータ構造にも自然に使えるようになること
です。
配列をStateで管理する ― 「画面の一覧=配列State」という視点の定着
一覧UIはほぼすべて配列Stateで表現されます
実務でReactを書いていると、 「一覧」を扱う場面が非常に多くなります。
- ユーザー一覧
- 商品一覧
- コメント一覧
- 通知一覧
これらはすべて、 「配列State+map+key」 という形で表現されることがほとんどです。
例として、ユーザー一覧を考えてみます。
import { useState } from "react";
function UserList() {
const [users, setUsers] = useState([
{ id: 1, name: "太郎", email: "taro@example.com" },
{ id: 2, name: "花子", email: "hanako@example.com" },
]);
return (
<div>
<h2>ユーザー一覧</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
<p>名前:{user.name}</p>
<p>メール:{user.email}</p>
</li>
))}
</ul>
</div>
);
}
export default UserList;
JSXここでの users は、
- 「今画面に表示されているユーザー一覧そのもの」
- つまり、UIの状態を表す配列State
です。
重要ポイント:
- 配列Stateは、「UIに出ている一覧」をそのまま表現していると考えると分かりやすいです。
- だからこそ、一覧を変えたいときは、配列Stateを更新することになります。
新しい配列を作る ― 「操作ごとにパターンを持つ」ことが大事です
追加・削除・更新・並び替えの4パターン
配列Stateを扱うとき、 よく出てくる操作は次の4つです。
- 追加
- 削除
- 更新
- 並び替え
それぞれ、「新しい配列を作る」ための基本パターンがあります。
追加:末尾に新しい要素を足す
const handleAddUser = () => {
const newUser = {
id: Date.now(),
name: "新しいユーザー",
email: "new@example.com",
};
setUsers((prevUsers) => [...prevUsers, newUser]);
};
JSX[...prevUsers, newUser]で「今の一覧+新しいユーザー」の配列を作っています。
削除:特定の要素を取り除いた新しい配列を作る
const handleDeleteUser = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
JSXfilter()で「削除したいユーザー以外」を残した新しい配列を作っています。
更新:特定の要素だけを書き換えた新しい配列を作る
const handleChangeUserName = (id, newName) => {
setUsers((prevUsers) =>
prevUsers.map((user) =>
user.id === id ? { ...user, name: newName } : user
)
);
};
JSXmap()で「同じ長さの新しい配列」を作り、 対象のユーザーだけ{ ...user, name: newName }でコピー+変更しています。
並び替え:ソートした新しい配列を作る
const handleSortByName = () => {
setUsers((prevUsers) =>
[...prevUsers].sort((a, b) => a.name.localeCompare(b.name))
);
};
JSX[...]でまずprevUsersをコピーし、 そのコピーに対してsort()を適用して、新しい配列を作っています。
重要ポイント:
- どの操作も、「元の配列を直接触らず、新しい配列を作る」という形になっています。
- 追加 → Spread
- 削除 → filter
- 更新 → map+オブジェクトSpread
- 並び替え → Spread+sort
というパターンを持っておくと、実務でも迷いなく書けるようになります。
Stateを直接変更しない ― 「バグの温床」を具体的にイメージする
直接変更すると何がまずいのか
配列Stateでよくある危険な書き方を、あえて示します。
// NG例:直接変更してしまう
const handleAddUser = () => {
const newUser = {
id: Date.now(),
name: "新しいユーザー",
email: "new@example.com",
};
users.push(newUser); // ← 元の配列を直接変更
setUsers(users); // ← 同じ配列を渡している
};
JSXこの書き方がまずい理由は、 「Reactが変化を正しく検知できないことがある」からです。
Reactは、
- 「前回のState」と「今回のState」が違うかどうか
を見て、再レンダリングするかどうかを決めます。
しかし、同じ配列オブジェクトを使い回していると、
- 「前回も今回も同じもの」と見なされる可能性があります。
- その結果、画面が更新されない、あるいは予期しない挙動になることがあります。
不変性(immutability)という考え方
この問題を避けるために、 Reactでは 「Stateは不変として扱う」 という考え方を採用しています。
「一度作ったStateは直接書き換えず、変更したいときは新しい値を作って置き換える」
というルールです。
- 配列なら、新しい配列を作る
- オブジェクトなら、新しいオブジェクトを作る
という形で、 「元のものは壊さず、新しいものを作る」ことが徹底されます。
重要ポイント:
- Stateを直接変更するコードは、「たまたま動いているだけ」で、非常に壊れやすいです。
- 不変性を守ることで、Reactの仕組みと自分のコードの考え方が揃い、バグを大きく減らせます。
Spread構文 ― ネストしたデータ構造にも使える「コピーの道具」
Spread構文は「コピーして新しいものを作る」ための記法です
Spread構文(...)は、 配列Stateを扱うときに頻出しますが、役割を一言で言うとこうです。
「今の配列やオブジェクトの中身をコピーして、新しいものを作るための記法」
配列に対するSpread
const arr = [1, 2, 3];
const newArr = [...arr, 4];
// newArr は [1, 2, 3, 4]
JavaScriptarrの中身をコピーして- その後ろに
4を追加して - 新しい配列
newArrを作っています。
オブジェクトに対するSpread
const user = { id: 1, name: "太郎", email: "taro@example.com" };
const updatedUser = { ...user, name: "太郎(更新済み)" };
// updatedUser は name だけ変わった新しいオブジェクト
JavaScriptuserの中身をコピーしてnameだけ上書きした新しいオブジェクトを作っています。
ネストした配列・オブジェクトでの使い方
実務では、 「ユーザー一覧の中にタスク一覧がある」といったネストした構造もよく出てきます。
const [users, setUsers] = useState([
{
id: 1,
name: "太郎",
tasks: [
{ id: 101, title: "メールを返信する", done: false },
{ id: 102, title: "資料を読む", done: true },
],
},
{
id: 2,
name: "花子",
tasks: [{ id: 201, title: "買い物に行く", done: false }],
},
]);
JSXこの中で、「太郎のタスクの完了状態を切り替える」処理を考えてみます。
const handleToggleTaskDone = (userId, taskId) => {
setUsers((prevUsers) =>
prevUsers.map((user) =>
user.id === userId
? {
...user,
tasks: user.tasks.map((task) =>
task.id === taskId ? { ...task, done: !task.done } : task
),
}
: user
)
);
};
JSXここでは、
prevUsers.map(...)で「ユーザー一覧の新しい配列」を作り- 対象のユーザーだけ
{ ...user, tasks: ... }でコピー+変更し - さらに
tasksの中でmap()とオブジェクトSpreadを使って、 対象のタスクだけdoneを反転させた新しいオブジェクトを作っています。
重要ポイント:
- ネストした構造でも、「元のものは壊さず、新しいものを作る」というルールは同じです。
- Spread構文は、ネストした配列・オブジェクトでも、「コピー+一部変更」を安全に行うための強力な道具です。
練習用テンプレート:ユーザー一覧+タスク一覧を配列Stateで管理する
最後に、DAY 25後半の内容をまとめた練習用テンプレートとして、 「ユーザー一覧+タスク一覧」を配列Stateで管理するコンポーネントを示します。
import { useState } from "react";
function UserTaskList() {
const [users, setUsers] = useState([
{
id: 1,
name: "太郎",
tasks: [
{ id: 101, title: "メールを返信する", done: false },
{ id: 102, title: "資料を読む", done: true },
],
},
{
id: 2,
name: "花子",
tasks: [{ id: 201, title: "買い物に行く", done: false }],
},
]);
const [nextUserId, setNextUserId] = useState(3);
const [nextTaskId, setNextTaskId] = useState(300);
const handleAddUser = () => {
const newUser = {
id: nextUserId,
name: `ユーザー${nextUserId}`,
tasks: [],
};
setUsers((prevUsers) => [...prevUsers, newUser]);
setNextUserId((prevId) => prevId + 1);
};
const handleAddTask = (userId) => {
const newTask = {
id: nextTaskId,
title: `新しいタスク${nextTaskId}`,
done: false,
};
setUsers((prevUsers) =>
prevUsers.map((user) =>
user.id === userId
? { ...user, tasks: [...user.tasks, newTask] }
: user
)
);
setNextTaskId((prevId) => prevId + 1);
};
const handleToggleTaskDone = (userId, taskId) => {
setUsers((prevUsers) =>
prevUsers.map((user) =>
user.id === userId
? {
...user,
tasks: user.tasks.map((task) =>
task.id === taskId ? { ...task, done: !task.done } : task
),
}
: user
)
);
};
const handleDeleteTask = (userId, taskId) => {
setUsers((prevUsers) =>
prevUsers.map((user) =>
user.id === userId
? {
...user,
tasks: user.tasks.filter((task) => task.id !== taskId),
}
: user
)
);
};
return (
<div>
<h2>ユーザー+タスク一覧(配列State 応用)</h2>
<button onClick={handleAddUser}>ユーザーを追加</button>
<ul style={{ marginTop: "8px" }}>
{users.map((user) => (
<li key={user.id}>
<h3>{user.name}</h3>
<button onClick={() => handleAddTask(user.id)}>
タスクを追加
</button>
<ul style={{ marginTop: "4px" }}>
{user.tasks.map((task) => (
<li key={task.id}>
<button
onClick={() => handleToggleTaskDone(user.id, task.id)}
>
{task.done ? "✅" : "⬜️"}
</button>
<span style={{ marginLeft: "8px", marginRight: "8px" }}>
{task.title}
</span>
<button
onClick={() => handleDeleteTask(user.id, task.id)}
>
削除
</button>
</li>
))}
</ul>
</li>
))}
</ul>
</div>
);
}
export default UserTaskList;
JSXこのテンプレートの中には、
- 配列をStateで管理:ユーザー一覧もタスク一覧も、配列Stateとして管理されています。
- 新しい配列を作る:追加・削除・更新のたびに、
[...]/filter()/map()で新しい配列を作っています。 - Stateを直接変更しない:
prevUsersやuser.tasksを直接書き換えず、常に新しい配列・オブジェクトを返しています。 - Spread構文:配列とオブジェクトの両方に対して、コピー+一部変更のために使われています。
DAY 25 後半のまとめ
DAY 25の後半では、
- 配列Stateが「画面に出ている一覧そのもの」を表しているという視点を、ユーザー一覧やタスク一覧を通して実務的な形で確認したこと
- 追加・削除・更新・並び替えを、それぞれ「新しい配列を作る」パターンとして整理し、実際のコードで使える形にしたこと
- Stateを直接変更することが、Reactの差分検知を壊し、バグの温床になる理由を具体的にイメージしたこと
- Spread構文を、「元のものを壊さずに、新しいものを作るための道具」として、ネストした配列・オブジェクトにも適用できる形で理解したこと
- ユーザー+タスク一覧のテンプレートを通して、 「配列State+新しい配列を作る+Stateを直接変更しない+Spread構文」 が、実務レベルのUIでもそのまま通用することを確認したこと
が大きなポイントになります。
ここまで来ると、
- 「配列Stateを扱うときは、必ず新しい配列を作る」
- 「Spread構文と配列メソッドが、そのための基本セット」
という感覚が、かなり強く自分の中に根付いているはずです。
