JavaScript/TypeScript

分割代入とスプレッド

4
  • JavaScript

分割代入とスプレッド — JavaScript Tips

分割代入(destructuring)は配列やオブジェクトから値を取り出す構文です。スプレッド(...)は展開、残余(rest)は残りをまとめる側です。コピー・マージと、浅いコピーの落とし穴があります。

参考: 分割代入 · スプレッド構文 · 残余引数


目次

  1. 配列の分割代入
  2. オブジェクトの分割代入
  3. デフォルト値と名前の変更
  4. ネストした構造の分解
  5. スプレッドでコピーとマージ
  6. 残余引数(rest)の 3 つの顔
  7. 関数の引数での活用
  8. 詰まりやすい点

配列の分割代入

配列の要素を個別の変数に一度に代入できます。

const colors = ["赤", "緑", "青"];const [first, second] = colors;console.log(first);  // "赤"console.log(second); // "緑"// 要素を飛ばすconst [, , third] = colors;console.log(third); // "青"

値の交換 も分割代入で簡潔に書けます。

let a = 1;let b = 2;[a, b] = [b, a];console.log(a, b); // 2 1

残余(rest) で残りの要素を配列として受け取れます。

const nums = [10, 20, 30, 40];const [head, ...tail] = nums;console.log(head); // 10console.log(tail); // [20, 30, 40]

オブジェクトの分割代入

オブジェクトは プロパティ名 に基づいて分解します。

const user = { id: 42, name: "山田", email: "yamada@example.com" };const { name, email } = user;console.log(name);  // "山田"console.log(email); // "yamada@example.com"

存在しないプロパティを分解すると undefined になります。あとでデフォルト値を設定する方法を説明します。

関数の戻り値がオブジェクトのとき、分割代入と相性がよいです。

function getSize() {  return { width: 800, height: 600 };}const { width, height } = getSize();console.log(`${width}x${height}`); // "800x600"

デフォルト値と名前の変更

デフォルト値 はプロパティが undefined のときだけ適用されます(null には適用されません)。

const { role = "guest", theme = "light" } = { role: "admin" };console.log(role);  // "admin"console.log(theme); // "light"const { count = 0 } = { count: null };console.log(count); // null(デフォルトは使われない)

変数名を変える(エイリアス) には プロパティ名: 新しい変数名 を使います。

const point = { x: 10, y: 20 };const { x: xPos, y: yPos } = point;console.log(xPos, yPos); // 10 20// x, y という変数は存在しない

エイリアスとデフォルトを組み合わせる書き方です。

const { status: httpStatus = 200 } = {};console.log(httpStatus); // 200

ネストした構造の分解

配列・オブジェクトが入れ子になっていても、同じ構造で分解できます。

const response = {  data: {    items: [      { id: 1, label: "A" },      { id: 2, label: "B" },    ],  },};const {  data: {    items: [, secondItem],  },} = response;console.log(secondItem.label); // "B"

深く分解しすぎると読みにくくなるので、よく使う部分だけ取り出し、残りは変数にまとめるバランスが大切です。

const { data } = response;const second = data.items[1];

スプレッドでコピーとマージ

スプレッド ...反復可能な値を展開 します。配列・オブジェクトのコピーやマージに使います。

配列

const base = [1, 2, 3];const extended = [0, ...base, 4];console.log(extended); // [0, 1, 2, 3, 4]const copy = [...base];copy.push(99);console.log(base); // [1, 2, 3] — 元は変わらない

オブジェクト

const defaults = { lang: "ja", theme: "light" };const overrides = { theme: "dark", fontSize: 14 };const settings = { ...defaults, ...overrides };console.log(settings);// { lang: "ja", theme: "dark", fontSize: 14 }// 後から書いた theme が優先される

オブジェクトのスプレッドは 浅いコピー(shallow copy) です。ネストしたオブジェクトは参照が共有されます。

const original = { meta: { count: 1 } };const cloned = { ...original };cloned.meta.count = 99;console.log(original.meta.count); // 99 — 中身は共有

残余引数(rest)の 3 つの顔

... は文脈によって スプレッド残余(rest) の 2 つの役割があります。残余は「残りをまとめて受け取る」側です。

1. 配列の分解

const [first, ...rest] = [1, 2, 3, 4];console.log(rest); // [2, 3, 4]

2. オブジェクトの分解

const person = { id: 1, name: "鈴木", age: 30, city: "大阪" };const { id, ...profile } = person;console.log(profile); // { name: "鈴木", age: 30, city: "大阪" }

id を除いた残りを profile にまとめるパターンは、API レスポンスの整形でよく使います。

3. 関数の残余引数

function sum(...numbers) {  return numbers.reduce((a, b) => a + b, 0);}console.log(sum(1, 2, 3, 4)); // 10

残余引数は 関数の最後の引数 にだけ置けます。

function log(level, ...messages) {  messages.forEach((msg) => console.log(`[${level}]`, msg));}log("info", "起動しました", "ポート 3000");

関数の引数での活用

分割代入は 関数の引数 に直接書くと、呼び出し側のオブジェクトから必要なプロパティだけを受け取れます。

function createUser({ name, role = "member", active = true }) {  return { name, role, active, createdAt: Date.now() };}createUser({ name: "田中" });// { name: "田中", role: "member", active: true, createdAt: ... }

オプションオブジェクトを受け取る関数では、このパターンが定番です。

function fetchData(url, { method = "GET", headers = {} } = {}) {  // method, headers を安全に使える  return { url, method, headers };}fetchData("/api/users");fetchData("/api/users", { method: "POST" });

第 2 引数全体のデフォルトに = {} を付けると、第 2 引数省略時のエラーを防げます。


詰まりやすい点

分解した変数が undefined になる

プロパティ名の スペルミス や、API のキー名の変更が典型原因です。

const { userName } = { username: "taro" };console.log(userName); // undefined(キーが username なのに userName で取っている)

スプレッドで「深いコピー」したつもりになる

const a = { nested: { x: 1 } };const b = { ...a };b.nested.x = 2;// a.nested.x も 2 になる

深いコピーが必要なら structuredClone(モダン環境)や、ライブラリ・自前の再帰コピーを検討します。

残余 rest の位置が間違っている

// NG: rest のあとに要素を置けない// const [...rest, last] = arr;// const [first, ...middle, last] = [1, 2, 3, 4]; // SyntaxError// OK: rest を最後に置き、必要なら後から末尾を取り出すconst [head, ...rest] = [1, 2, 3, 4];const last = rest.pop();

配列では rest は 最後の要素 にだけ置けます。

オブジェクト rest で意図せずプロパティが残る

const item = { type: "book", title: "JS入門", internal: true };const { internal, ...publicItem } = item;// publicItem から internal を除きたい — OK// NG: 公開用なのに秘密のキーを忘れているconst { title } = item;sendToClient(item); // internal ごと送ってしまう

デフォルト値と || の混同

const { port } = { port: 0 };const p1 = port || 3000;  // 3000(0 は falsy なので上書きされる)const p2 = port ?? 3000;  // 0(nullish だけ置き換え)

0"" を有効な値として扱うなら、デフォルト値には ?? を使う場面があります。

シェア