【JS / TS】スプレッド構文は、オブジェクトや配列を展開するだけじゃない

# 1.はじめに

スプレッド構文って、オブジェクトや配列を簡単に一括で表示する程度の認識しか持っていませんでした。

@uhyo さんの「プロを目指す人のための TypeScript 入門」を元にキャッチアップしました。

スプレッド構文について、知らなかったことがあったので、アウトプットします。

少し長い記事ですが、スプレッド構文についてまとめるとこのボリュームになりました。  
気になるところだけでも見て頂ければなと思います。

間違って解釈している所ありましたら、ご指摘いただけますと幸いです。




# 2.目次
[1. はじめに](#1はじめに)  
[2. 目次](#2目次)  
[3. この記事でわかること](#3この記事でわかること)  
[4. 環境](#4環境)  
[5. スプレッド構文とは](#5スプレッド構文とは)  
　[5.1. スプレッド構文とは](#51スプレッド構文とは)  
　[5.2. スプレッド構文の使いかた](#52スプレッド構文の使いかた)  
　[5.3. 別のオブジェクトや配列からコピー](#53別のオブジェクトや配列からコピー)  
　[5.4. スプレッド構文は、オブジェクトや配列の拡張に便利](#54スプレッド構文はオブジェクトや配列の拡張に便利)  
　[5.5. スプレッド構文は複数使用できる](#55スプレッド構文は複数使用できる)  
　[5.6. ネストしているものは、コピーされない](#56ネストしているものはコピーされない)  
[6. おわりに](#6おわりに)  
[7. 参考](#7参考)  




# 3.この記事でわかること

:::note info

スプレッド構文は、オブジェクトや配列を展開するだけという認識から脱却できる

- 別のオブジェクトや配列から **コピーする**
- スプレッド構文でコピーしたオブジェクトや配列は、**別の** オブジェクトや配列となる
- スプレッド構文を使用する時は、**`{}`、`[]`** で囲む
- スプレッド構文でコピーしたものには **変更が反映されない** ※ ネスト除く
- スプレッド構文よりも **前に** プロパティーの変更を行っても変更できない
- 1つのオブジェクトや配列の中で **複数使用できる**
- 同じプロパティがあった時は、**後の方** が採用される
- **ネスト** しているものは、**コピーされない**

:::




# 4.環境
- Node.js: 16.15.1


# 5.スプレッド構文とは




## 5.1.スプレッド構文とは
スプレッド構文とは、何かを調べてみると、

> 配列式や文字列などの反復可能オブジェクトを、0 個以上の引数 (関数呼び出しの場合) や要素 (配列リテラルの場合) を期待された場所で展開したり、オブジェクト式を、0 個以上のキーと値の組 (オブジェクトリテラルの場合) を期待された場所で展開したりすることができます。  
>  
> // 一部省略  
>  
> スプレッド構文の真価は、オブジェクトや配列などに含まれる要素の数に関係なく、同じ値で動作することにあります

とドキュメントに記載がありました。

[参照: MDN Web Docs](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax)

つまり、

:::note info

別のオブジェクトや配列を **コピー** して、展開する

:::



僕は、**コピーする** ことを理解していませんでした。




## 5.2.スプレッド構文の使いかた

スプレッド構文は、オブジェクトや配列の中に `...〇〇〇` と記述して使います。




### 5.2.1.オブジェクトのとき

```js:オブジェクトのスプレッド構文（使いかた）
const daishiInfo = {
	name: 'daishi',
	age: 35
};

// オブジェクトのスプレッド構文
const whoAreYou = { ...daishiInfo };

console.log(whoAreYou);  // { name: 'daishi', age: 35 }
```

`daishiInfo` オブジェクトの中身がすべて `whoAreYou` に入っています。




### 5.2.2.配列のとき

```js:配列のスプレッド構文（使いかた）
const daishiSkills = ['JavaScript', 'TypeScript'];

// 配列のスプレッド構文
const whatSkills = [...daishiSkills];

console.log(whatSkills);  // [ 'JavaScript', 'TypeScript' ]
```

オブジェクト同様に、`daishiSkills` の配列の中身がすべて `whatSkills` に入っています。


スプレッド構文は、オブジェクトや配列の **展開** なので、  
使用する時は、`{}`、 `[]` で囲む必要があります。


```js:スプレッド構文（間違った使いかた）
const whoAreYou  = ...daishiInfo;
const whatSkills = ...daishiSkills;
```
 
とすると、エラーになります。

スプレッド構文は、簡単にオブジェクトや配列をコピーすることができますが、  
挙動について理解していないと、**意図しない** バグの原因になりかねないので記事にしました。




## 5.3.別のオブジェクトや配列からコピー

まずは、**コピー** とは、どういうことか見ていきます。




### 5.3.1.オブジェクトのとき

```js:オブジェクトのスプレット構文（コピーとは？）
const daishiInfo = {
	name: 'daishi',
	age: 35
};

// オブジェクトのスプレッド構文
const whoAreYou = { ...daishiInfo };

// name に 'manju' を再代入
daishiInfo.name = 'manju';

console.log(daishiInfo); // { name: 'manju', age: 35 }
console.log(whoAreYou);  // { name: 'daishi', age: 35 }
```

`daishiInfo` オブジェクトの `name` プロパティに `manju` を再代入しました。

**コピー元** の `daishiInfo` は、  
`{ name: 'daishi', age: 35 }` → `{ name: 'manju', age: 35 }`   
に変化しましたが、

**コピー先** の `whoAreYou` は、  
`{ name: 'daishi', age: 35 }`  のままです。

これが **コピーする** ということです。

:::note info

スプレッド構文でコピーしたものには **コピー元の変更が反映されない**

:::

コピー元の変更が反映されないと言うことは、別のオブジェクトということになります。

```js:オブジェクトのスプレッド構文（一致判定）
const daishiInfo = {
	name: 'daishi',
	age: 35
};

// オブジェクトのスプレッド構文
const whoAreYou = { ...daishiInfo };

// 別のオブジェクトかを確認
console.log(daishiInfo === whoAreYou); // false
```

`daishiInfo` と `whoAreYou` は、一致していないことがわかりました。

:::note info

コピーするということは、**別の**オブジェクトを作るということ

:::






### 5.3.2.配列のとき

```js:配列のスプレッド構文（コピーとは？）
const daishiSkills = ['JavaScript', 'TypeScript', 'PHP', 'Ruby'];

// 配列のスプレッド構文
const whatSkills = [...daishiSkills];

// ２ 要素目に 'Java' を再代入
daishiSkills[2] = 'Java';

console.log(daishiSkills); // [ 'JavaScript', 'TypeScript', 'Java', 'Ruby' ]
console.log(whatSkills);   // [ 'JavaScript', 'TypeScript', 'PHP', 'Ruby' ]
```

こちらも、オブジェクトのときと同様に、
`daishiSkills[2]` 要素に `Java` を再代入しました。

**コピー元** の `daishiSkills` は、  
`[ 'JavaScript', 'TypeScript', 'PHP', 'Ruby' ]` → `[ 'JavaScript', 'TypeScript', 'Java', 'Ruby' ]`  
と変化していますが、

**コピー先** の `whatSkills` は、  
`[ 'JavaScript', 'TypeScript', 'PHP', 'Ruby' ]` のままです。

コピー元の変更が反映されないと言うことは、別の配列ということになります。

```js:配列のスプレッド構文（一致判定）
const daishiSkills = ['JavaScript', 'TypeScript', 'PHP', 'Ruby'];

// 配列のスプレッド構文
const whatSkills = [...daishiSkills];

// 配列が一致しているかを確認
console.log(daishiSkills === whatSkills); // false
```

`daishiSkills` と `whatSkills` は、一致していないことがわかりました。


※ オブジェクト同様、**ネスト** した配列はコピーされません。




## 5.4スプレッド構文は、オブジェクトや配列の拡張に便利

スプレッド構文は、オブジェクトや配列の拡張や書き換え、新しいものを作るのに便利です。  
ただし、スプレッド構文の記述位置によっては、データが書き換えが反映されない箇所もあるので、注意が必要になります。

細かく見ていきたいと思います。




### 5.4.1.オブジェクトのとき

```js:オブジェクトのスプレッド構文（スプレッド構文の位置）
const daishiInfo = {
	name: 'daishi',
	age: 35,
	email: 'daishi@example.com',
};

// オブジェクトのスプレッド構文
const manjuInfo = {
	name: 'manju',
	...daishiInfo,
	email: 'manju@example.com',
	sex: 'male',
};

console.log(manjuInfo); // { name: 'daishi', age: 35, email: 'manju@example.com', sex: 'male' }
```

オブジェクトの `name`、`email` プロパティの値を書き換え、`sex` プロパティの値を追加しようとしましたが、
`name` プロバティの値のみ書き換えが反映できてません。

これは、`...daishiInfo` のスプレッド構文よりも **前に記述する** か **後ろに記述する** かで反映されるデータが変わるからです。

`name: 'manju'` は、スプレッド構文の `...daishiInfo` よりも **前に記述**しています。  
コピー元の `daishiInfo` の `name: 'daishi'` が、`name: 'manju'` を書き換えています。  
なので、`name: 'manju'` は **反映されていません** 。

次に、  
`email: 'manju@example.com'` は、スプレッド構文の `...daishiInfo` よりも **後ろに記述**しています。  
`email: 'manju@example.com'` が、コピー元の `daishiInfo` の `email: 'daishi@example.com'` を書き換えています。  
なので、`email: 'manju@example.com'` は反映されています。

最後に、  
`sex: 'male'` は、`daishiInfo` には存在しなかったので、`sex: 'male'` が反映されています。

ロジックは、前から後ろに読んでいくので、当たり前と言えば当たり前ですが、僕はこれを理解できていませんでした…

:::note info

コピー先の値の変更は、**スプレッド構文の後ろ** に記述する

:::

この記述方法であれば、コピー元には反映されません。




### 5.4.2.配列のとき

```js:配列のスプレッド構文（スプレッド構文の位置）
const daishiSkills = ['JavaScript', 'TypeScript', 'PHP', 'Ruby'];

// 配列のスプレッド構文
const manjuSkills = ['React', ...daishiSkills, 'Java'];

console.log(daishiSkills); // [ 'JavaScript', 'TypeScript', 'PHP', 'Ruby' ]
console.log(manjuSkills);  // [ 'React', 'JavaScript', 'TypeScript', 'PHP', 'Ruby', 'Java' ]
```

配列のときは、オブジェクトとは挙動が異なります。

:::note info

配列のスプレッド構文は、**その配列の要素すべて** が、その位置にコピーされる

:::

なので `manjuSkills` は、`[ 'React', 'JavaScript', 'TypeScript', 'PHP', 'Ruby', 'Java' ]` の通り、
- `0` 要素目と `5` 要素目は、値が追加
- `1` 要素目から `4` 要素目までは、`daishiSkills` の配列がコピー  
となっているのが、わかるかと思います。




## 5.5.スプレッド構文は複数使用できる

スプレッド構文は、1つのオブジェクトや配列に対して、複数できます。  
ただしこちらも、オブジェクトと配列では挙動が異なります。




### 5.5.1.オブジェクトのとき

```js:オブジェクトのスプレッド構文（複数の使用）
const daishiInfo = {
	name: 'daishi',
	age: 35,
	email: 'daishi@example.com',
};

const manjuInfo = {
	name: 'manju',
	age: 132,
	sex: 'female',
};

// オブジェクトの複数のスプレッド構文
const whoAreYou: UserInfo = {
	...daishiInfo,
	...manjuInfo,
};

console.log(whoAreYou); // { name: 'manju', age: 132, email: 'daishi@example.com', sex: 'female' }
```

この2つのオブジェクトをスプレッド構文でつなげてみます。

ご覧の通り、複数のスプレッド構文を問題なく使用できます。

ただ、注意点があり、

- `name` プロバティは、`manjuInfo` の値
- `age` プロバティは、`manjuInfo` の値
- `email` プロバティは、`daishiInfo` の値
- `sex` プロバティは、`manjuInfo` の値

となっています。

これは、 
- **`name` と `age` プロバティは、`daishiInfo` の値が、`manjuInfo` に上書きされる**
- `email` プロバティは、`daishiInfo` にしかないので、`daishiInfo` の値
- `sex` プロバティは、`manjuInfo` にしかないので、`manjuInfo` の値

という理由からです。

:::note info

同じプロパティは、後の方が採用される

:::

定義されていないプロパティに関しては、定義されている方が採用されます。




### 5.5.2.配列のとき

配列に関しても、複数のスプレッド構文を使用できます。

```js:配列のスプレッド構文（複数の使用）
const daishiSkills = ['JavaScript', 'TypeScript', 'PHP', 'Ruby'];
const manjuSkills  = ['JavaScript', 'TypeScript', 'React', 'PHP'];

// 配列の複数のスプレッド構文
const programmingSkills = [
	...daishiSkills,
	...manjuSkills
];

console.log(programmingSkills); // ['JavaScript', 'TypeScript', 'PHP', 'Ruby', 'JavaScript', 'TypeScript', 'React', 'PHP']
```

ご覧の通り、オブジェクトと違い、

:::note info

同じ要素番号に同じ値が入っていても両方の値が採用される

:::




## 5.6.ネストしているものは、コピーされない

ネストしているオブジェクトや配列は、コピーされません。

思わぬところで値が変わるおそれがあるので、ネストしたスプレッド構文は、取り扱いに注意が必要です。




### 5.6.1.オブジェクトのとき

```js:オブジェクトのスプレッド構文（ネスト ダメな使いかた）
const daishiProfile =
	{
		name: 'daishi',
		age: 35,
		programmingSkills: {
			frontEnd: ['JavaScript', 'TypeScript'],
		}
	};

// オブジェクトのスプレッド構文
const whoAreYou = {
	...daishiProfile
};

// daishiProfile に再代入
daishiProfile.name = 'manju';
daishiProfile.programmingSkills.frontEnd = ['PHP', 'Ruby'];

console.log(daishiProfile); // { name: 'manju',  age: 35, programmingSkills: { frontEnd: ['PHP', 'Ruby'] } }
console.log(whoAreYou);     // { name: 'daishi', age: 35, programmingSkills: { frontEnd: ['PHP', 'Ruby'] } }
```

上記は、 
`whoAreYou` オブジェクトに `daishiProfile` オブジェクトをスプレッド構文でコピーした後に、   
`daishiProfile` の `name` プロバティと`programmingSkills.frontEnd` プロパティに再代入しました。

ここで、思いも寄らない、値が返ってきます。

`whoAreYou` は、　　
`{ name: 'daishi', age: 35, programmingSkills: { frontEnd: ['PHP', 'Ruby'] } }` となってほしいところ、
`{ name: 'daishi', age: 35, programmingSkills: { frontEnd: ['JavaScript', 'TypeScript'] } }` となっています。

つまり、  
コピー先の `whoAreYou` の `name` プロバティは再代入 **前** の `'daishi'` が入っていますが、  
`programmingSkills.frontEnd` プロパティは再代入 **後** の、`{ frontEnd: ['PHP', 'Ruby'] }` が入っています。 

このことから、

:::note warn

**ネストしたオブジェクトはコピーされない** 

:::

ことがわかります。


では、ネストした値もコピーしたいときはどうするかと言うと、  
**ネストした部分もスプレッド構文** で囲ってあげます。

```ts:オブジェクトのスプレッド構文（ネスト 正しい使いかた）
const daishiProfile =
	{
		name: 'daishi',
		age: 35,
		programmingSkills: {
			frontEnd: ['JavaScript', 'TypeScript'],
		}
	};

// ネストしたオブジェクトのスプレッド構文
const whoArtYou = {
	...daishiProfile,
	programmingSkills: { ...daishiProfile.programmingSkills }
;

// daishiProfile に再代入
daishiProfile.name = 'manju';
daishiProfile.programmingSkills.frontEnd = ['PHP', 'Ruby'];

console.log(daishiProfile); // { name: 'manju',  age: 35, programmingSkills: { frontEnd: ['PHP', 'Ruby'] } }
console.log(whoArtYou);     // { name: 'daishi', age: 35, programmingSkills: { frontEnd: ['JavaScript', 'TypeScript'] } }
```

ネストした部分もスプレッド構文で囲ってあげると、  
コピー元に再代入しても、**コピー先は変わっていない** のがわかるかと思います。

**（書き方まどろっこしいな…）**  
こころの声が…


:::note info

ネストしたオブジェクトをコピーする際は、ネスト部分もスプレッド構文で囲む必要がある

:::




### 5.6.2.配列のとき

配列も、オブジェクト同様に、ネストしているとコピーされません。

```js:配列のスプレッド構文（ネスト ダメな使いかた）
const daishiSkills = [['JavaScript', 'TypeScript'],['PHP', 'Ruby']];

// 配列のスプレッド構文
const whatSkills = [...daishiSkills];

// daishiSkills に再代入
daishiSkills[1][0] = 'Java';

console.log(daishiSkills); // [ [ 'JavaScript', 'TypeScript' ], [ 'Java', 'Ruby' ] ]
console.log(whatSkills);   // [ [ 'JavaScript', 'TypeScript' ], [ 'Java', 'Ruby' ] ]
```

コピー元の `daishiSkills` の `PHP` を `Java` に再代入で変更しました。

変更しないで欲しいコピー先の `whatSkills` も `Java` に変化してます。


オブジェクトのとき同様にネストした配列をコピーするときは、  
**ネストした部分もスプレッド構文** で囲ってあげます。

```js:配列のオブジェクト（ネスト 正しい使いかた）
const daishiSkills = [['JavaScript', 'TypeScript'],['PHP', 'Ruby']];

// // ネストした配列のスプレッド構文
const whatSkills = [[...daishiSkills[0]], [...daishiSkills[1]]];

// オブジェクトのスプレッド構文
daishiSkills[1][0] = 'Java';

console.log(daishiSkills); // [ [ 'JavaScript', 'TypeScript' ], [ 'Java', 'Ruby' ] ]
console.log(whatSkills);   // [ [ 'JavaScript', 'TypeScript' ], [ 'PHP', 'Ruby' ] ]
```

ネストした部分をスプレッド構文で囲ってあげると、  
コピー元に再代入しても、**コピー先は変わっていない** のがわかるかと思います。



[ ↓ 追記 ↓ ]
### 5.6.3 ネスト部分もコピーできるライブラリ

@RyoTa_0222 さんが、このまどろっこしい書き方を解決するライブラリを教えてくださりました。  
ありがとうございます。

簡単にネスト部分もコピーできるんですね。便利！！

https://github.com/angus-c/just

https://flaviocopes.com/how-to-clone-javascript-object/

［ ↑ 追記 ↑ ］

# 6.おわりに

スプレッド構文は、オブジェクトや配列を展開するだけと思っていましたが、  
調べてみると奥が深いものだなと感じました。

特に、コピーしているのか、同じものを呼んでいるのかについての認識をもっていなかったです。

思いも寄らないところでバグを起こしかねないので意識しなければと言う思いと、今までのコード大丈夫かなという恐怖でいっぱいです。

問題あれば、早急に対応します…

もし、この記事で間違った認識してるよってところがあれば、ご指摘頂けますと幸いです。


併せて他の記事も読んでいただけると嬉しいです🙇‍♂️

https://qiita.com/daishiman/items/5acf803f252b82f970fe

https://qiita.com/daishiman/items/324dcc0eae72eb6b13cf

https://qiita.com/daishiman/items/d382279a2a6cae3ea1aa

# 7.参考
[書籍：プロを目指す人のためのTypeScript入門　鈴木僚太［著］](https://twitter.com/uhyo_/status/1517158176365641729?s=20&t=5jZBSZVbLrAsgkCUruO7Ig) @uhyo
[MDN Web Docs](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax)