今さら分割代入知らないなんて言えないのでTypeScriptでこっそり勉強する

`({ id }) => {}` とか `{ id } = params` とか、あーこういう書き方できるよねってぼんやりした知識はあったんですが、分割代入って何？何ができるの？って言われてみるときちんと知りませんでした。今さら分割代入知らないとか言えないので、こっそり MDN のページを読みつつ TypeScript と絡めて勉強します。

> MDN web docs **分割代入**
> https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

# 環境（2020/01/19追記）

下記のソースコードは TypeScript playground（v3.7）で動作を確認しました。
https://www.typescriptlang.org/play/#

# （１）配列

## 基本的な使い方

基本的な使い方はこのような感じです。変数宣言と同時に配列の要素の値を代入する事ができます。

```typescript
const [a, b] = [1, 2];

a; // 1
b; // 2
```

`let` で宣言すると再代入が可能です。

```typescript
let [a, b] = [1, 2];
a = 100;

a; // 100
```

## 型推論

右辺の配列リテラルは TypeScript ではタプル型とみなされます。

```typescript
let [a, b, c] = [1, 'abc', true];　// [number, string, boolean]
```

つまり、このような代入をしているのと同じです。

```typescript
const tuple: [number, string, boolean] = [1, 'abc', true];

let a = tuple[0]; // number
let b = tuple[1]; // string
let c = tuple[2]; // boolean
```

コンパイラにより型がチェックされるため、違う型の値を再代入する事はできません。

```typescript
let [a, b, c] = [1, 'abc', true];

// コンパイルエラー
a = 'some text'; // Type '"some text"' is not assignable to type 'number'.
b = true; // Type 'true' is not assignable to type 'string'.
c = 100; // Type '100' is not assignable to type 'boolean'.

```

## 代入先の要素数より配列リテラルの要素数が少ない場合

コンパイルエラーが発生します。

```typescript
// Tuple type '[number]' of length '1' has no element at index '1'.
const [a, b] = [1];
```

コンパイルエラーはデフォルト値を設定する事で回避できます。

```typescript
const [a, b = 'none'] = [1];

a; // 1
b; // 'none'
```

## 代入先の要素数より配列リテラルの要素数が多い場合

残りの要素は代入されません。

```typescript
const [a, b] = [1, 2, 3, 4, 5];

a; // 1
b; // 2
```

スプレッド構文を使うと、残りの要素をまとめて拾う事ができます。

```typescript
const [a, b, ...c] = [1, 2, 3, 4, 5];

a; // 1
b; // 2
c; // [3, 4, 5]
```

## 要素を無視する

興味のない要素は、変数名を省略する事で無視する事ができます。

```typescript
const [a,,,, b] = [1, 2, 3, 4, 5];

a; // 1
b; // 5
```

## ネストした配列

このような記述で値をフラットに取得できます。

```typescript
const [a, b, [c, d]] = [1, 2, [3, 4]];
a; // 1
b; // 2
c; // 3
d; // 4
```

## 配列の分割代入を使用する例

実際の運用コードでは `[1, 2]` のような要素が固定された配列はあんまり登場しませんよね。では、どんなときに配列の分割代入を使用するメリットがあるのでしょうか？

それはたぶん **配列の順番に意味がある時** ではないかと思います。例えば下記のように、同じ情報が繰り返し出現するテキストがあるとします。

```typescript
const log = `
* Version: v1.0
* Date: 2020-01-31
* Changelog:
  - bugfix
  - update deps

* Version: v0.9
* Date: 2020-01-22
* Changelog:
  - bugfix

* Version: v0.8
* Date: 2019-12-15
* Changelog:
  - add feature
`;
```

テキストは「バージョン」「日付」「アップデート内容」の順番で、情報が繰り返されます。ここから「日付」だけをピックアップしてみましょう。

```typescript
function getHistory (logs: string[]): string[] {
  const [, date, , ...rest] = logs;

  return rest.length ?
    [date].concat(getHistory(rest)) :
    [date]
  ;
}

const history = getHistory(log.split('*').splice(1));
// [
//   'Date: 2020-01-31', 
//   'Date: 2020-01-22', 
//   'Date: 2019-12-15'
// ]
// ※補足：splitした時に先頭に改行が入るので、spliceで除外
```

「配列の順番に意味がある時」って他にもありますよね。例えば正規表現によるテキストのマッチングにも使えそうです。

```typescript
const regExp = new RegExp('[0-9]+', 'g');
const userInput = '0000-111-2222';

const [
  areaCode, 
  localAreaCode, 
  subscriberNumber
] = (userInput.match(regExp) || []) as string[];

areaCode + localAreaCode + subscriberNumber;
// 00001112222
```

どうでしょう。 `[0][1]` などの添字を使った従来のアクセスより、シンプルで意図が伝わるコードになったのではないでしょうか。

# （２）オブジェクト

## 基本的な使い方

配列と同じく、変数宣言と同時にオブジェクトの要素の値が代入できます。

```typescript
const { a, b } = { a: 1, b: 'abc' };

a; // 1
b; // 'abc'
```

`let` で宣言すると再代入が可能です。

```typescript
let { a, b } = { a: 1, b: 'abc' };
a = 100;

a; // 100
```

変数宣言と代入を別の行に分ける事もできます。その際、左辺の `{}` がブロックとみなされシンタックスエラーになるため、行全体に `()` を使ってオブジェクトリテラルとみなされるようにします。

```typescript
let a: number, b: string;

// シンタックスエラー
{ a, b } = { a: 1, b: 'abc' };

// OK
({ a, b } = { a: 1, b: 'abc' });
```

## 型推論

オブジェクトの分割代入は、このような代入をしているのと同じです。

```typescript
let a = 1;
let b = 'abc';
```

コンパイラにより型がチェックされるため、違う型の値を再代入する事はできません。

```typescript
let { a, b } = { a: 1, b: 'abc' };

// コンパイルエラー
a = 'some text'; // '"some text"' is not assignable to type 'number'.
b = 100; // Type '100' is not assignable to type 'string'.
```

## 興味のある値のピックアップ

このようなオブジェクトから値をピックアップしてみましょう。

```typescript
const user = {
  id: 1,
  userName: 'ringtail003',
  email: 'ringtail003@gmailcom',
  config: {
    plan: {
      planName: 'Basic',
      paymentMethod: 'visa',
    },
  },
};
```

トップの階層から値をピックアップする時は、キー名と同じ名前の変数を宣言するだけです。

```typescript
const { id, userName } = user;

id; // 1
userName; // 'ringtail003'
```

下の階層から値をピックアップする時は、構造を示します。

```typescript
const { 
  config: { 
    plan: { 
      planName,
    }
  }
} = user;

planName; // 'Basic'
```

代入元のオブジェクトに存在しない変数を指定した場合、コンパイルエラーが発生します。型安全でいいですね。

```typescript
const { 
  config: { 
    plan: { 
      // コンパイルエラー
      // Property 'point' does not exist on type '{ planName: string; paymentMethod: string; }'.
      point,
    }
  }
} = user;
```

## デフォルト値

分割代入の左辺には型アノテーションを指定する事ができます。

```typescript
let { a, b }: { a: number, b: string } = { a: 1, b: 'abc' };
```

これを利用すると、オプショナルな値にデフォルト値を設定する事ができます。

```typescript
interface User { 
  id: number;
  userName: string;
  email?: string; // Optional
}

const user = {
  id: 1,
  userName: 'ringtail003',
};

const {
  id,
  userName,
  email = '設定されていません',
}: User = user;

email; // '設定されていません'
```

## リネーム

分割代入の左辺では、代入元・代入先の変数の名前を別々に指定する事ができます。

```typescript
let { a: myValue, b } = { a: 1, b: 'abc' };
myValue; // 1
```

これを利用すると、オブジェクトのキー名のリネームが簡単に処理できるようになります。

```typescript
const ajaxResponse = {
  id: 1,
  user_name: 'ringtail003',
  email_address: 'ringtail003@gmail.com',
};

const {
  id,
  user_name: userName,
  email_address: email,
} = ajaxResponse;

const model = { id, userName, email };

model;
// {
//   id: 1,
//   userName: 'ringtail003',
//   email: 'ringtail003@gmail.com',
// }
```

さらに型アノテーションを指定しておけば、代入元のオブジェクトの形が代わった時にコンパイルエラーが得られるので、型安全になりますね。

```diff
+ interface AjaxResponse { 
+   id: number;
+   user_name: string;
+   email_address: string;
+ }
...

const {
  id,
  user_name: userName,
  email_address: email,
- } = ajaxResponse;
+ }: AjaxResponse = ajaxResponse;
```

# （３）関数

関数では引数に分割代入を使用する事ができます。

```typescript
function getItem({ name = '' }) {
  return ({ name });
}
```

上記の関数は、引数として与えられたオブジェクトから `name` をピックアップし、新しいオブジェクトを返すものです。

```typescript
const items = [
  { id: 1, name: 'foo' },
  { id: 2, name: 'bar' },
];

items.map((item) => getItem(item));
// [
//   { name: 'foo' },
//   { name: 'bar' },
// ]
```

分割代入で空のオブジェクトを与えると、関数の呼び出し側で引数を省略する事ができます。

```typescript
function getItem({ name = 'default' } = {}) {
  return ({ name });
}

getItem();
// { name: 'default' }
```

## 関数の分割代入を使用する例

下記のコードは、会議室と自転車のレンタル情報を示すものです。

```typescript
interface MeetingRoom {
  name: string;
  charge: number;
  capacity: number;
}

const meetingRooms = [
  { name: '大会議室', charge: 8000, capacity: 100 },
  { name: '小会議室', charge: 1500, capacity: 10 },
];

interface Bicycle {
  name: string;
  charge: number;
  inMaintenance: boolean;
}

const bicycles = [
  { name: 'ロードバイク', charge: 500, inMaintenance: true },
  { name: 'ママチャリ', charge: 300, inMaintenance: false },
];
```

会議室と自転車がごちゃまぜになった「レンタル用品リスト」があるとします。

```typescript
const list: (MeetingRoom | Bicycle)[] = [
  meetingRooms[0],
  meetingRooms[1],
  bicycles[0],
  bicycles[1],
];
```

このレンタル用品リストから「名前」「利用料金」と、自転車にしか存在しない「メンテナンス中フラグ」を取り出してみましょう。

```typescript
function getItem({ 
  name = '', 
  charge = 0, 
  inMaintenance = undefined,
}) {
  return { name, charge, inMaintenance };
}

list.map((item) => getItem(item));
// [
//   { name: '大会議室', charge: 8000, inMaintenance: undefined },
//   { name: '小会議室', charge: 1500, inMaintenance: undefined },
//   { name: 'ロードバイク', charge: 500, inMaintenance: true },
//   { name: 'ママチャリ', charge: 300, inMaintenance: false },
// ]
```

引数に型アノテーションを指定しておくと会議室にも自転車にも存在しないプロパティをコンパイルエラーにできるため、より安全になります。

```diff
function getItem({ 
  name = '', 
  charge = 0, 
  inMaintenance = undefined,
- } {
+ }: Partial<MeetingRoom & Bicycle>) {
  return { name, charge, inMaintenance };
```

分割代入を使わない場合でも同じ処理を書いてコンパイルエラーを得る事ができます。ただし「この関数がどのプロパティを必要としているか」は関数内部のコードを読まないと分からないですよね。この点、分割代入ではシグネチャでそれを示す事ができるのがメリットではないかと思います。

```typescript
function getItem(item: Partial<MeetingRoom & Bicycle>) { 
  return {
    name: item.name,
    charge: item.charge,
    inMaintenance: item.inMaintenance
  };
}
```

# おわり

以上でおわりです。
MDN のドキュメントを読んだだけでは「ふーん、そんな書き方できるんだ」とあまり新しい発見はなさそうに思ったのですが、TypeScript で書いてみると MDN のサンプルコードを書き換えないとコンパイルが通らない部分があったり、なかなかいろんな発見があって面白かったです。

これで「分割代入...知らない |ω・｀)」状態からは抜け出せそうです！