Help us understand the problem. What is going on with this article?

【TypeScript/JavaScript】Optional Chainingのトランスパイル後がわからなかったので調べてみた

こちらの記事で、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 0undefinedと等価

代入式の返り値は「代入した値」

まず前半部分について注目してみます。

(_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|undefinednullでは型がそれぞれ異なるためこの式の結果は必ずfalseになります。

つまりこの式は、_afoo.bar.bazを代入した上で、||以降の式を必ず実行する仕掛けになっています。

void 0undefinedと等価

次に後半部分です。

_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が書き換え可能である」というのが理由の一つとしてあると思います。

参考: void(0)とundefinedの使い分け

なぜundefinedの代わりに使うのか?

jsではundefinedは値であり、すなわち上書きができる。そのため代わりとして、voidが使われる。

example
console.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の書き換えがトランスパイルに影響されないようになっています。

まとめ

以上より、トランスパイル後のコードは「_afoo.bar.bazを代入し、_aundefinedでなければ_aのプロパティnameを返す。そうでなければundefinedを返す」という挙動であることがわかりました。

(※ でも、foo.bar.bazを直接比較せずに_aに代入してから比較するのはなんでだろう。。。。分かる人いたら教えてください。)

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away