3秒でわかる
配列やオブジェクトの中身を3点リーダーで展開する記法。元を壊さずコピーや結合を作れるので、状態更新の定番になっています。
もう少し詳しく
どういうものか
スプレッド構文は、... を前に付けて配列やオブジェクトの要素を1つずつ取り出し、その場に並べる書き方です。配列リテラルの中で使えば要素が展開され、オブジェクトリテラルの中で使えばキーと値が展開され、関数呼び出しの引数として使えば1つずつの引数になります。
見た目が同じ ... でも、関数の仮引数の位置に書いたものは残余引数と呼ばれ、逆に複数の引数を1つの配列に集めます。展開と収集で向きが反対です。
なぜ必要か
React の状態や Redux のような仕組みでは、元のオブジェクトを書き換えるのではなく、新しいオブジェクトを作って渡す必要があります。直接書き換えると、変更前と後が同じ参照になり、画面の再描画が起きません。スプレッドは、この「元を残したまま一部だけ違うものを作る」操作を1行で書ける手段です。
具体例
const base = [1, 2, 3];
const added = [...base, 4]; // [1, 2, 3, 4] 元の base は変わらない
const user = { id: 1, name: "佐藤", tags: ["新規"] };
const renamed = { ...user, name: "鈴木" }; // name だけ差し替えた新オブジェクト
// 関数呼び出しで展開する
const nums = [5, 12, 3];
Math.max(...nums); // 12
// 仮引数側は逆に集める (残余引数)
function sum(...values) {
return values.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6つまずきやすいところ
スプレッドのコピーは1段だけです。上の renamed は新しいオブジェクトですが、renamed.tags は user.tags と同じ配列を指しています。renamed.tags.push("常連") と書くと、user.tags も増えます。入れ子を含めて分けたいなら、内側もスプレッドするか structuredClone を使います。
オブジェクトを展開するとき、同じキーが2回出たら後に書いたほうが勝ちます。{ ...user, name: "鈴木" } と { name: "鈴木", ...user } では結果が逆になり、後者は元の名前に戻ります。順序の取り違えは静かに動くぶん見つけにくいバグです。
似た用語との違い
| 書き方 | 向き | 例 |
|---|---|---|
| スプレッド | 1つを複数へ展開 | f(...arr) |
| 残余引数 | 複数を1つへ集約 | function f(...args) |
| 分割代入 | 中身を取り出して名前を付ける | const [a, b] = arr |
いずれも ... や [] を使う見た目が似ているので、書いてある場所で判断します。