Optional Chaining とは
オブジェクト構造の奥深くにあるプロパティ値を参照する際、途中のプロパティが存在するかどうかのチェックをコードに書かなくても、安全に処理してくれる仕組み。
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
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. の README に載っている。
Optional Chaining を使うソースコードを準備する
以下の「foo?.bar?.baz」的なコードを hello.ts というファイルに保存する。
interface には Optional Properties (存在しない可能性もあるオプション的なプロパティ) なプロパティとして bar と baz を定義している。
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 が以下の内容で出力される。
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 かどうかチェックするのが手間なので、省略してしまって以下のような危険なコードを書きがち。
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