TypeScript 3.7 から導入される予定の Optional Chaining を試す

## Optional Chaining とは

オブジェクト構造の奥深くにあるプロパティ値を参照する際、途中のプロパティが存在するかどうかのチェックをコードに書かなくても、安全に処理してくれる仕組み。

[tc39/proposal\-optional\-chaining](https://github.com/tc39/proposal-optional-chaining)

> The Optional Chaining Operator allows a developer to handle many of those cases without repeating themselves and/or assigning intermediate results in temporary variables:
> 
> var street = user.address?.street

上の例では、address が undefined であってもエラーは発生せず、street 変数には undefined がセットされるだけとなる。

Optional Chaining が使えない状況では以下のように自前でチェックするコードを書く必要があった。

```
var street = user.address && user.address.street;
```

## Optional Chaining は TypeScript 3.7 で導入される予定 (2019年11月リリース予定)

[TypeScript 3\.7 Iteration Plan · Issue \#33352 · microsoft/TypeScript](https://github.com/microsoft/TypeScript/issues/33352)

> November 5th
> TypeScript 3.7 Final Release

## Optional Chaining の実行環境を用意する

TypeScript の nightly builds には Optional Chaining は含まれていなかった (2019年9月27日現在)。

そのため TypeScript ソースコードの git リポジトリを clone して、Optional Chaining が組み込まれた optionalChainingStage3 ブランチをビルドする。

```
$ git clone https://github.com/microsoft/TypeScript.git

$ cd TypeScript

$ git checkout optionalChainingStage3

$ npm install -g gulp

$ npm install

$ gulp local

$ cd ..

$ node TypeScript/built/local/tsc.js --version
Version 3.7.0-dev
```

TypeScript のソースコードをビルドする方法は公式リポジトリ [microsoft/TypeScript: TypeScript is a superset of JavaScript that compiles to clean JavaScript output\.](https://github.com/microsoft/TypeScript/) の README に載っている。

## Optional Chaining を使うソースコードを準備する

以下の「foo?.bar?.baz」的なコードを hello.ts というファイルに保存する。
interface には Optional Properties (存在しない可能性もあるオプション的なプロパティ) なプロパティとして bar と baz を定義している。

```ts
interface Foo {
  name: string
  bar?: {
    name: string
    baz?: {
      name: string
    }
  }
}

let foo: Foo

foo = {name: 'a', bar: {name: 'bar', baz: {name: 'baz'}}}
console.log(foo?.bar?.baz)

foo = {name: 'a', bar: {name: 'bar'}}
console.log(foo?.bar?.baz)

foo = {name: 'a'}
console.log(foo?.bar?.baz)
```

## Optional Chaining の挙動を確認する

TypeScript の tsc コマンドで JavaScript のコードに変換する。

```
$ tsc hello.ts 
```

hello.js が以下の内容で出力される。

```js
var _a, _b, _c, _d, _e, _f;
var foo;
foo = { name: 'a', bar: { name: 'bar', baz: { name: 'baz' } } };
console.log((_b = (_a = foo) === null || _a === void 0 ? void 0 : _a.bar) === null || _b === void 0 ? void 0 : _b.baz);
foo = { name: 'a', bar: { name: 'bar' } };
console.log((_d = (_c = foo) === null || _c === void 0 ? void 0 : _c.bar) === null || _d === void 0 ? void 0 : _d.baz);
foo = { name: 'a' };
console.log((_f = (_e = foo) === null || _e === void 0 ? void 0 : _e.bar) === null || _f === void 0 ? void 0 : _f.baz);
```

Node.js で実行する。

```
$ node hello.js
{ name: 'baz' }
undefined
undefined
```

チェーン途中のオブジェクトに undefined が存在しても、Cannot read property などのエラーが発生せずに実行できている。

## Optional Chaining が使えない場合には危険なコードを書いてしまう

Optional Chaining が使えない場合は、辿る変数が undefined かどうかチェックするのが手間なので、省略してしまって以下のような危険なコードを書きがち。

```ts
let foo: Foo

foo = {name: 'a', bar: {name: 'bar', baz: {name: 'baz'}}}
console.log(foo.bar.baz)

foo = {name: 'a', bar: {name: 'bar'}}
console.log(foo.bar.baz)

foo = {name: 'a'}
console.log(foo.bar.baz)
```

これを実行すると bar が undefined の場合に「TypeError: Cannot read property 'baz' of undefined」が発生してしまう。

```
$ tsc hello.ts

$ node hello.js
{ name: 'baz' }
undefined
/Users/me/hello.js:7
console.log(foo.bar.baz);
                    ^

TypeError: Cannot read property 'baz' of undefined
```

## 参考資料

- [Optional Chaining (Stage 3 Draft / September 27, 2019)](https://tc39.es/proposal-optional-chaining/)
- [tc39/proposal\-optional\-chaining (Optional Chaining for JavaScript)](https://github.com/tc39/proposal-optional-chaining)
- [Suggestion: "safe navigation operator", i\.e\. x?\.y · Issue \#16 · microsoft/TypeScript](https://github.com/microsoft/TypeScript/issues/16)
- [microsoft/TypeScript at optionalChainingStage3](https://github.com/microsoft/TypeScript/tree/optionalChainingStage3)
- [Optional Properties - Interfaces · TypeScript](https://www.typescriptlang.org/docs/handbook/interfaces.html#optional-properties)