スプレッド構文でオブジェクトをマージする

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

スプレッド構文でオブジェクトをマージする とは

オブジェクトのスプレッド構文でデフォルト値をマージし、設定オブジェクトを安全に組み立てる方法を学ぶ。本レッスンでは、スプレッド構文でオブジェクトをマージする の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

スプレッド構文でオブジェクトをマージする

配列で活躍したスプレッド構文は、オブジェクトでもそのまま使えます。ES2018 で追加された オブジェクトスプレッド は、const merged = { ...a, ...b } の形で 2 つのオブジェクトを結合できます。設定オブジェクトをデフォルト値で埋めたり、不変的にプロパティを更新したりと、現代の JavaScript では避けて通れない記法です。

オブジェクトスプレッドは「プロパティを並べる」操作。後から書いたキーが前のキーを上書きする。

基本のマージ

2 つのオブジェクトをマージするには、新しいオブジェクトリテラルの中に ... を並べます。

JavaScript

const defaults = { theme: "light", size: "md" }; const overrides = { size: "lg" }; const config = { ...defaults, ...overrides }; console.log(config); // { theme: "light", size: "lg" }

後に書いた overridessize が、先に書いた defaultssize上書きします。これが「デフォルト値マージ」の基本パターンです。

JavaScript

// Before const config = Object.assign({}, defaults, overrides); // After const config = { ...defaults, ...overrides };

順序が決定的に重要。{ ...override, ...defaults } と書くと defaults が勝つ ことになる。

不変な更新 (immutable update)

ReactRedux、関数型のスタイルでは、元のオブジェクトを変更せず、新しいオブジェクトを返すのが基本です。スプレッドは、まさにそのために生まれた構文と言ってもいいです。

JavaScript

const user = { name: "Alice", age: 28 }; const updated = { ...user, age: 29 }; console.log(user); // { name: "Alice", age: 28 } console.log(updated); // { name: "Alice", age: 29 }

user はそのまま、updatedage だけ書き換わった新しいオブジェクトです。元のデータを破壊しないので、ReactsetStateuseState でも安全に使えます。

動きを追ってみる

diagram (will load when visible)

図のとおり、左から順にプロパティが追加され、同じキーがあれば後勝ちで上書きされます。元のオブジェクトはどちらも変わりません。

Object.assign との違い

Object.assign(target, ...sources) も似た動きをしますが、第 1 引数 (target) を直接書き換える点に注意が必要です。

JavaScript

const a = { x: 1 }; Object.assign(a, { y: 2 }); console.log(a); // { x: 1, y: 2 } ← 破壊された const a2 = { x: 1 }; const b2 = { ...a2, y: 2 }; console.log(a2); // { x: 1 } ← 変わらない

破壊を避けたいなら Object.assign({}, a, b) のように空オブジェクトを target に置きます。しかしスプレッドの方が短く読みやすいので、現代では { ...a, ...b } が主流です。

Object.assign を直接呼ぶと target が破壊されるので注意。スプレッドなら勘違いが起こりにくい。

ネストはシャローコピーであることに注意

オブジェクトスプレッドも配列同様、シャローコピーです。ネストしたオブジェクトは参照を共有します。

JavaScript

const src = { profile: { name: "Alice" } }; const copy = { ...src }; copy.profile.name = "Bob"; console.log(src.profile.name); // "Bob" ← src も変わる

ネスト構造を完全に独立させるには、内側もスプレッドで作り直すか、structuredClone(obj) を使います。

よくある間違い

第 1 に、配列に {...arr} を使うとオブジェクト化されます。{...[1,2]}{ "0": 1, "1": 2 } になります。第 2 に、スプレッドの順序を逆にすると、想定と違う上書きが起こります。「デフォルト → 上書き」の順に書きます。第 3 に、nullundefined をスプレッドしてもエラーにならず無視されます。これは便利ですが、デバッグのときに気付きにくいので注意です。

やってみよう

デフォルト設定 defaults とユーザー設定 userConfig を受け取り、両方をマージしたオブジェクトを返す mergeConfig 関数を書きましょう。userConfig のキーが defaults を上書きする挙動になります。return { ...defaults, ...userConfig } の 1 行で完成です。React のコンポーネントに props を渡すときも、同じ書き方で { ...defaultProps, ...props } のように使われます。

よくある質問

Q. スプレッド構文の典型的な使い道は?

A. 配列・オブジェクトのコピー([...arr]、{...obj})、結合([...a, ...b])、関数引数の展開(fn(...args))が代表例です。深いコピーはできない点に注意し、ネスト構造を完全複製したいときは structuredClone(obj) を使ってください。

Q. スプレッドとレストの違いは?

A. 見た目は ... で同じですが、配列を展開するのがスプレッド、引数を集めるのがレスト(残余引数)です。function fn(...args) のように関数定義側で使うとレスト、fn(...arr) のように呼び出し側で使うとスプレッドになります。

Q. オブジェクトを展開するときの上書き優先順位は?

A. { ...a, ...b } と書くと b のキーが a を上書きします。デフォルト値を先に展開し、ユーザー設定を後ろで上書きする { ...defaults, ...userOpts } が定番イディオムです。順序を間違えるとデフォルト値が勝ってしまうので注意してください。

次のレッスン

次は find と includes で配列を検索する で、オブジェクトのスプレッド構文でデフォルト値をマージし、設定オブジェクトを安全に組み立てる方法を学ぶ を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. スプレッドObject の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. スプレッドObject とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. スプレッド構文 ... を使ってマージすること
  2. userConfig のキーが defaults を上書きすること
  3. 元の 2 つのオブジェクトを破壊しないこと

入出力例

test-cases.txt

mergeConfig({"size":"md","theme":"light"}, {"size":"lg"}){"size":"lg","theme":"light"} mergeConfig({"size":"md","theme":"light"}, {}){"size":"md","theme":"light"} mergeConfig({"theme":"light"}, {"lang":"ja"}){"lang":"ja","theme":"light"} mergeConfig({"a":1,"b":2}, {"a":10,"b":20}){"a":10,"b":20}

ヒント

main.js
main.js
学習モード

メモ

スプレッド構文でオブジェクトをマージする

⌘S で保存