テンテンテン:JavaScriptとReactにおけるスプレッド演算子

テンテンテン:JavaScriptとReactにおけるスプレッド演算子

Michael Charles Aubrey // Fri May 19 2023

JavaScriptのスプレッド演算子

スプレッド演算子は配列やオブジェクトの要素を個々に分割し、新たなコンテキストに展開するための便利な道具です。

配列とスプレッド演算子

まず、配列と一緒にスプレッド演算子をどのように使用するかを見てみましょう。

例えば、以下のような配列があるとします。

let arr1 = [1, 2, 3];

これを新しい配列内でスプレッドしたいときには、スプレッド演算子を使います。

let arr2 = [...arr1, 4, 5, 6];
console.log(arr2); // Output: [1, 2, 3, 4, 5, 6]

スプレッド演算子は関数呼び出しの引数としても使用することができます。以下に例を示します。

let numbers = [1, 2, 3];
console.log(Math.max(...numbers)); // Output: 3

オブジェクトとスプレッド演算子

オブジェクトに対してもスプレッド演算子を利用できます。オブジェクトのプロパティを別のオブジェクトにコピーしたり、既存のオブジェクトに新たなプロパティを追加したりする場合に便利です。

以下にオブジェクトの例を示します。

let obj1 = {a: 1, b: 2};

このオブジェクトを新しいオブジェクトにスプレッドしたい場合、以下のようにします。

let obj2 = {...obj1, c: 3};
console.log(obj2); // Output: {a: 1, b: 2, c: 3}

既存のオブジェクトのプロパティを上書きする場合にもスプレッド演算子を使えます。例えば、以下のようにすることで、obj1b の値を 3 に変更できます。

let obj3 = {...obj1, b: 3};
console.log(obj3); // Output: {a: 1, b: 3}

以上がJavaScriptのスプレッド演算子の基本的な使い方です。

参照型とスプレッド演算子

JavaScriptの配列やオブジェクトは参照型のデータであり、これらはメモリ上の特定の場所(参照)を指すように動作します。例えば、あるオブジェクトを別のオブジェクトに代入すると、両者は同じメモリ上のデータを参照します。そのため、一方を変更すると、他方も影響を受けます。

let obj1 = { name: "山田" };
let obj2 = obj1;
obj2.name = "田中"; 
console.log(obj1.name); // 田中

上記の例では、obj2を通じてデータを変更したにも関わらず、obj1の値も変更されました。これはobj1obj2が同じ参照を共有しているためです。

しかし、スプレッド演算子を使用すると、新しい配列やオブジェクトを生成し、元の配列やオブジェクトの値を新しい配列やオブジェクトにコピーすることができます。この操作により、新しい配列やオブジェクトは元のデータの新しい参照となり、元のデータを直接変更することなく新しいデータを操作できます。

Reactでは、状態の不変性が非常に重要です。状態を直接変更すると、Reactが状態の変更を正確に追跡できず、予期せぬバグやパフォーマンスの問題を引き起こす可能性があります。スプレッド演算子を使用して新しい配列やオブジェクトを作成することで、この問題を回避し、状態の変更を正確に追跡し、適切に再レンダリングを行うことができます。

React 状態に関連したその使用法について

Reactの状態(state)は、コンポーネントが自身のデータを作成し管理するための組み込み機能です。そのため、状態の値が変更されると、コンポーネントは再レンダリングされ、UIが更新されます。状態は単純な文字列から、複雑なオブジェクトや配列まで扱うことが可能です。フック(Hooks)と関数コンポーネントを使用して配列やオブジェクトの状態を更新する方法とその理由について、スプレッド演算子を用いた例と共に説明します。

配列の状態とスプレッド演算子

useState フックを使用して配列の状態を作成し、スプレッド演算子を使って新しい項目を追加する例を以下に示します。

export const Products = () => {
  const [products, setProducts] = useState(["りんご", "みかん", "ばなな"]);

  const addProduct = (product) => {
    const newProducts = [...products];
    newProducts.push(product);
    setProducts(newProducts);
  };

  return (
    <div>
      <ul>
        {products.map((product, i) => (
          <li key={i}>{product}</li>
        ))}
      </ul>
      <button
        onClick={() => {
          addProduct("いちご");
        }}
      >
        いちごを追加
      </button>
    </div>
  );
};

useStateフックを用いてproductsの初期状態( ["りんご", "みかん", "ばなな"] )を設定しています。そして、addProductという新しい商品を追加するための関数を定義しています。

スプレッド演算子はこのaddProduct関数内で使用されています。const newProducts = [...products];の行で、スプレッド演算子を使って現在のproducts配列の要素を新しい配列newProductsにコピーしています。これによりnewProductsproductsと同じ要素を持つ新しい配列となります。

その後、newProducts.push(product);newProducts配列に新たな商品を追加し、setProducts(newProducts);でこの更新した配列を新たな状態として設定します。

このスプレッド演算子の使用が重要な理由は、Reactの状態の不変性(Immutability)に関連しています。つまり、既存の状態(ここではproducts配列)を直接変更するのではなく、新たな配列(newProducts)を作成して状態を更新します。これにより、Reactは状態の変更を追跡しやすくなり、状態が更新された時に効率的に再レンダリングできます。

また、ボタンをクリックするとaddProduct関数が呼び出され、"いちご"が商品リストに追加されます。その結果、商品リストは再レンダリングされ、新たに追加された"いちご"が表示されます。

オブジェクトの状態とスプレッド演算子

同様に、useState フックを使ってオブジェクトの状態を作成し、スプレッド演算子を用いて新しいオブジェクトを作成する例を示します。

import { useState } from "react";

export const UserProfile = () => {
  const [user, setUser] = useState({
    name: "山田",
    age: 25,
    email: "yamada@example.com",
  });

  const handleAgeChange = (newAge) => {
    const newUser = { ...user };
    newUser.age = newAge;
    setUser(newUser);
  };

  return (
    <div>
      <p>Name: {user.name}</p>
      <p>Age: {user.age}</p>
      <p>Email: {user.email}</p>
      <button
        onClick={() => {
          handleAgeChange(user.age + 1);
        }}
      >
        年齢を1歳上げる
      </button>
    </div>
  );
};

useStateフックを用いてuserの初期状態を設定し、handleAgeChange関数を定義しています。この関数ではスプレッド演算子を使用してuserオブジェクトのプロパティを新しいオブジェクト(newUser)にコピーします。次に、newUserの年齢を新たな年齢(newAge)に更新し、setUserでこの新しいユーザーオブジェクトを状態として設定します。

このスプレッド演算子の使用は、状態の不変性を保つために重要です。新しいオブジェクトを作成してから状態を更新することで、Reactは状態の変更を効率的に追跡できます。

また、ボタンをクリックすると、handleAgeChange関数が呼び出され、ユーザーの年齢が1歳上がります。これにより、ユーザープロファイルの表示が更新されます。

自分で試そう:挑戦編

スプレッド演算子について理解を深めるため、以下の挑戦を試してみてください。

挑戦1: 配列の結合

以下の2つの配列があるとします:

let array1 = [1, 2, 3];
let array2 = [4, 5, 6];

スプレッド演算子を使用して、これら2つの配列を結合した新しい配列を作成してみてください。

解答

挑戦2: 配列のコピー

次に、以下の配列があります:

let array = [1, 2, 3, 4, 5];

スプレッド演算子を使用して、上記の配列のコピーを作成してみてください。

解答

挑戦3: オブジェクトのコピー

最後の挑戦として、スプレッド演算子を使用してオブジェクトをコピーしてみましょう。以下のオブジェクトがあります:

let person = { name: '太郎', age: 20 };

スプレッド演算子を使用して、personオブジェクトのコピーを作成し、その上で名前を '次郎' に変更してみてください。

解答

この記事は元々 Qiita に掲載されています。