
テンテンテン:JavaScriptとReactにおけるスプレッド演算子
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}
既存のオブジェクトのプロパティを上書きする場合にもスプレッド演算子を使えます。例えば、以下のようにすることで、obj1 の b の値を 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の値も変更されました。これはobj1とobj2が同じ参照を共有しているためです。
しかし、スプレッド演算子を使えば、新しい配列やオブジェクトを作って、そこに元の値をコピーできます。新しく作った側は別の参照を持つので、元のデータを直接変えずに中身を操作できます。
React では状態の不変性が欠かせません。状態を直接書き換えると、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にコピーしています。その結果、newProductsはproductsと同じ要素を持つ別の配列になります。
その後、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: "[email protected]",
});
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 に掲載されています。