こちらの記事で、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
に代入してから比較するのはなんでだろう。。。。分かる人いたら教えてください。)