こちらの記事で、TypeScript
v3.7からの新機能「Optional Chaining」が解説されていました。
その中に「Optional Chaining」を使用したコードのトランスパイル結果が記載されています。
例えばこんなコードがあるとして
interface Foo { name: string bar: { name: string baz?: { name: string } } } let foo: Foo foo = { name: 'a', bar: { name: 'bar' } } console.log(foo.bar.baz?.name)トランスパイルするとどうなるかというと下記のようになります。
var _a; let foo; foo = { name: 'a', bar: { name: 'bar' } }; console.log((_a = foo.bar.baz) === null || _a === void 0 ? void 0 : _a.name);
このトランスパイル後のソース、特に4行目のコード
console.log((_a = foo.bar.baz) === null || _a === void 0 ? void 0 : _a.name);
がどのような文法で成り立っているかわからなかったので、調査してみました。
※: 記事内に間違っている箇所があればコメントで教えてくださるとたすかります。
前提条件
以下の文法については理解している前提とします。
-
==と===の違い - 三項演算子
ポイント
- 代入式の返り値は「代入した値」
-
void 0はundefinedと等価
代入式の返り値は「代入した値」
まず前半部分について注目してみます。
(_a = foo.bar.baz) === null
JavaScriptでは、代入式の返り値は「代入した値」になります。つまり、(_a = foo.bar.baz)の返り値はfoo.bar.bazです。
また、interface Fooの定義により 、foo.bar.bazの型はTypeScript上ではstring|undefinedであるため、
左辺(_a = foo.bar.baz)の返り値は何らかの文字列もしくはundefinedです。
その返り値の値をnullと厳密比較している、というのが上式の意味です。
ここで、===による厳密比較がtrueになるには両辺の型が一致している必要がありますが、
string|undefinedとnullでは型がそれぞれ異なるためこの式の結果は必ずfalseになります。
つまりこの式は、_aにfoo.bar.bazを代入した上で、||以降の式を必ず実行する仕掛けになっています。
void 0はundefinedと等価
次に後半部分です。
_a === void 0 ? void 0 : _a.name
void 0という見慣れない文法がありますが、これはundefinedと等価です。
上式をundefinedで置き換えるとわかりやすくなります。
_a === undefined ? undefined : _a.name
_aがundefinedならundefined、そうでないなら_a.nameを返す式ですね。
undefinedでなくvoid 0を使用するのは、「undefinedが書き換え可能である」というのが理由の一つとしてあると思います。
なぜundefinedの代わりに使うのか?
jsではundefinedは値であり、すなわち上書きができる。そのため代わりとして、voidが使われる。
exampleconsole.log(a === undefined) // true console.log(a === void 0) // true // undefinedをうわがく var undefined = 100 console.log(a === undefined) // false console.log(a === void 0) // true
void 0を使用することでundefinedの書き換えがトランスパイルに影響されないようになっています。
まとめ
以上より、トランスパイル後のコードは「_aにfoo.bar.bazを代入し、_aがundefinedでなければ_aのプロパティnameを返す。そうでなければundefinedを返す」という挙動であることがわかりました。
(※ でも、foo.bar.bazを直接比較せずに_aに代入してから比較するのはなんでだろう。。。。分かる人いたら教えてください。)