モダンJavaScriptの基礎から始める挫折しないためのReact入門 60日コース | Stateを使いこなす - DAY 25:配列State

React 60日で身につけるReact
スポンサーリンク
スポンサーリンク
  1. DAY 25:配列State 前半 ― 「Stateで配列を扱う」ための土台を固める
  2. 配列をStateで管理するとはどういうことか
    1. 「今の状態」を配列で表現する
  3. 新しい配列を作る ― 「書き換え」ではなく「置き換え」
    1. State更新は「新しい値に置き換える」イメージ
  4. Stateを直接変更しない ― なぜダメなのか
    1. 「直接書き換える」とReactが変化を検知できないことがある
    2. 「不変性(immutability)」という考え方
  5. Spread構文で「新しい配列」を自然に作る
    1. Spread構文とは何か
    2. タスク追加の例
    3. 削除や更新でもSpread構文+配列メソッドを使う
  6. 練習用テンプレート:配列Stateでタスクを追加する
  7. DAY 25 前半のまとめ
  8. DAY 25:配列State 中盤 ― 「追加・削除・更新」を安全に扱うための配列思考
  9. 配列をStateで管理する ― 「一覧そのものがState」という感覚
    1. 単なる「変数」ではなく「今の状態」
  10. 新しい配列を作る ― 追加・削除・更新の基本パターン
    1. 1. 追加:末尾に新しい要素を足す
    2. 2. 削除:特定の要素を取り除いた新しい配列を作る
    3. 3. 更新:特定の要素だけを書き換えた新しい配列を作る
  11. Stateを直接変更しない ― 「やってはいけない書き方」を具体的に見る
    1. よくあるNGパターン:push や直接代入
    2. 正しいパターンとの対比
  12. Spread構文を「配列コピー+追加」の道具として理解する
    1. Spread構文の役割を言葉で説明する
    2. ReactのState更新での使い方
  13. 練習用テンプレート:配列Stateで「追加・削除・完了切り替え」をまとめて扱う
  14. DAY 25 中盤のまとめ
  15. DAY 25:配列State 後半 ― 「実務レベルで通用する配列Stateの感覚」を仕上げる
  16. 配列をStateで管理する ― 「画面の一覧=配列State」という視点の定着
    1. 一覧UIはほぼすべて配列Stateで表現されます
  17. 新しい配列を作る ― 「操作ごとにパターンを持つ」ことが大事です
    1. 追加・削除・更新・並び替えの4パターン
  18. Stateを直接変更しない ― 「バグの温床」を具体的にイメージする
    1. 直接変更すると何がまずいのか
    2. 不変性(immutability)という考え方
  19. Spread構文 ― ネストしたデータ構造にも使える「コピーの道具」
    1. Spread構文は「コピーして新しいものを作る」ための記法です
    2. ネストした配列・オブジェクトでの使い方
  20. 練習用テンプレート:ユーザー一覧+タスク一覧を配列Stateで管理する
  21. 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 },
];
JSX

Reactでこの「タスク一覧」を画面に表示し、 さらに「追加・削除・更新」などをしたい場合、 この配列を 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

ここで起きていることは、

  1. prevTasks に「今のタスク一覧」が入っている
  2. [...] を使って、「今のタスク一覧を展開」している
  3. その後ろに newTask を追加して、「新しい配列」を作っている
  4. 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

ここでの流れは、

  1. newTask に「追加したいタスク」を作る
  2. prevTasks に「今のタスク一覧」が入る
  3. [...prevTasks, newTask] で「今の一覧+新しいタスク」の配列を作る
  4. それを 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));
};
JSX
  • filter() で「削除したいユーザー以外」を残した新しい配列を作っています。

更新:特定の要素だけを書き換えた新しい配列を作る

const handleChangeUserName = (id, newName) => {
  setUsers((prevUsers) =>
    prevUsers.map((user) =>
      user.id === id ? { ...user, name: newName } : user
    )
  );
};
JSX
  • map() で「同じ長さの新しい配列」を作り、 対象のユーザーだけ { ...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]
JavaScript
  • arr の中身をコピーして
  • その後ろに 4 を追加して
  • 新しい配列 newArr を作っています。

オブジェクトに対するSpread

const user = { id: 1, name: "太郎", email: "taro@example.com" };
const updatedUser = { ...user, name: "太郎(更新済み)" };
// updatedUser は name だけ変わった新しいオブジェクト
JavaScript
  • user の中身をコピーして
  • 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を直接変更しないprevUsersuser.tasks を直接書き換えず、常に新しい配列・オブジェクトを返しています。
  • Spread構文:配列とオブジェクトの両方に対して、コピー+一部変更のために使われています。

DAY 25 後半のまとめ

DAY 25の後半では、

  • 配列Stateが「画面に出ている一覧そのもの」を表しているという視点を、ユーザー一覧やタスク一覧を通して実務的な形で確認したこと
  • 追加・削除・更新・並び替えを、それぞれ「新しい配列を作る」パターンとして整理し、実際のコードで使える形にしたこと
  • Stateを直接変更することが、Reactの差分検知を壊し、バグの温床になる理由を具体的にイメージしたこと
  • Spread構文を、「元のものを壊さずに、新しいものを作るための道具」として、ネストした配列・オブジェクトにも適用できる形で理解したこと
  • ユーザー+タスク一覧のテンプレートを通して、 「配列State+新しい配列を作る+Stateを直接変更しない+Spread構文」 が、実務レベルのUIでもそのまま通用することを確認したこと

が大きなポイントになります。

ここまで来ると、

  • 「配列Stateを扱うときは、必ず新しい配列を作る」
  • 「Spread構文と配列メソッドが、そのための基本セット」

という感覚が、かなり強く自分の中に根付いているはずです。

タイトルとURLをコピーしました