TypeScriptのOptional Chainingは用法用量を守って正しく使え

## TypeScriptの3.7.2
TypeScriptの新バージョンが11月にリリースされました。

[Announcing TypeScript 3.7 \| TypeScript](https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/)

私自身、特に注目していたのが `Optional Chaining` でした。
簡単に説明をすると下記のように書くことができます。

``` typescript
let x = foo?.bar.baz();
```

今までであればこんな書き方としていたものや

``` typescript
let x = (foo === null || foo === undefined) ?
    undefined :
    foo.bar.baz();

```

他にもこんな書き方をしていたものを簡潔に書くことができて、とても人に優しい書き方ができますね。

``` typescript
let x = foo && foo.bar.baz()
```

実際に私も携わっているプロジェクトで `foo && foo.bar && foo.bar.hoge` というようにしなければいけないことが多く、この見にくいコードから開放されれば良いと `3.7` のリリースをまだかまだかと思っていました。そういった開発者の方は多いのではないかなと思います。

今回はReactを使ったプロジェクトのTypeScriptのバージョンをあげ環境を整える部分と、実際に使ってみた感想と注意点をご紹介できればと思います。

## 始める際の留意点

大体の場合下記の手順でできるはずです。

1. tsのバージョンを上げる
2. @babel/plugin-proposal-optional-chainingを導入
3. VSCodeのワークスペースのTSバージョンを上げる
4. prettierを最新バージョンにする

1と2はいいとして3、4は私もちょっとハマったとこなのでピックアップして説明しますね。すでに環境が整っている方はここは飛ばしてしまって良いです。

### VSCodeのワークスペースのTSバージョンを上げる
VS Codeを使っている場合VS CodeのワークスペースのTSバージョンを変更しないとエラーになってしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47844/4353219d-26ac-69bf-d43e-276c67c3586e.png)


エラーを解消するにはVS Codeの右下の方にある数字の部分をクリック（ここだと3.6.3）して `3.7.2` を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47844/faca9183-e780-d1c4-655f-361cc00c640a.png)



### prettierを最新バージョンにする

2019/11/08 現在だとnpmにprettierの最新バージョンが上がっていません。が、githubのmasterでは `3.7.2` に対応しているのでそちらを使うようにします。

`"prettier": "github:prettier/prettier#master"` 



ここまでやれば実際に `Optional Chaining` を使う環境ができたと思います。

2019/11/11追記
prettier v1.9 でOptional Chainingのサポートがされたようです。
https://github.com/prettier/prettier/blob/master/CHANGELOG.md#1191

## トランスパイルするとどうなんの？
使うことができたんですが、トランスパイルされたコードはどうなっているのでしょう？トランスパイルされたコードも確認してみましょう。

[TypeScript Playground](http://www.typescriptlang.org/play/)  でコードを書きながら試すことができます。

例えばこんなコードがあるとして

``` typescript
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)
```

トランスパイルするとどうなるかというと下記のようになります。

``` typescript
var _a;
let foo;
foo = { name: 'a', bar: { name: 'bar' } };
console.log((_a = foo.bar.baz) === null || _a === void 0 ? void 0 : _a.name);
```

### 完全に勘違いしていた

私の知識不足なんですが、トランスパイルされたコードは普通にこうなるかと思っていました。（一緒に働いているエンジニアさんも勘違いしていたみたい）

``` typescript
foo.bar.baz && foo.bar.baz.name
```

ここでひとつ懸念が・・・

「これ、使いすぎるとバンドルサイズやばくね？？」
「あれ？じゃあ、実行速度はどうなんよ？？」

SPAプロジェクトにおいてバンドルサイズは大敵です。もちろんServiceWorkerでキャッシュさせて初回以降のRenderを速くすることは可能ですが、バンドルサイズが大きければ大きいほど初回renderは遅くなってしまいます。

ってことで簡単なテストですが、バンドルサイズと実行速度の検証をしてみました。

## 検証

実際のReactのプロジェクトにコードを書いてテストをしてみました。

### `Optional Chaining` を使わないパターン

単純に `&&` で評価してくパターンです。正直可読性よくない。

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

let foo: Foo

const startTime = performance.now() // 開始時間
foo = { name: 'a', bar: { name: 'bar' } }

console.log(foo && foo.bar && foo.bar.baz && foo.bar.baz.name)
console.log(foo && foo.bar && foo.bar.baz && foo.bar.baz.name)
console.log(foo && foo.bar && foo.bar.baz && foo.bar.baz.name)
console.log(foo && foo.bar && foo.bar.baz && foo.bar.baz.name)
console.log(foo && foo.bar && foo.bar.baz && foo.bar.baz.name)
console.log(foo && foo.bar && foo.bar.baz && foo.bar.baz.name)
console.log(foo && foo.bar && foo.bar.baz && foo.bar.baz.name)
console.log(foo && foo.bar && foo.bar.baz && foo.bar.baz.name)
console.log(foo && foo.bar && foo.bar.baz && foo.bar.baz.name)
console.log(foo && foo.bar && foo.bar.baz && foo.bar.baz.name)

const endTime = performance.now() // 終了時間
```

バンドルサイズ
`11.09 KB build/static/js/main.566b4901.chunk.js`

実行速度
`2.094999999826541`

### Optional Chainingを使う

``` typescript
const startTime2 = performance.now() // 開始時間

foo = { name: 'a', bar: { name: 'bar' } }

console.log(foo?.bar?.baz?.name)
console.log(foo?.bar?.baz?.name)
console.log(foo?.bar?.baz?.name)
console.log(foo?.bar?.baz?.name)
console.log(foo?.bar?.baz?.name)
console.log(foo?.bar?.baz?.name)
console.log(foo?.bar?.baz?.name)
console.log(foo?.bar?.baz?.name)
console.log(foo?.bar?.baz?.name)
console.log(foo?.bar?.baz?.name)

const endTime2 = performance.now() // 終了時間
console.log('after *****************')
console.log(endTime2 - startTime2)
console.log('*****************')
```

バンドルサイズ
`11.42 KB (+347 B)  build/static/js/main.87dedbcc.chunk.js`

実行速度
`1.4400000000023283`

思った通りバンドルサイズは大きくなりました。簡単なテストっていう部分はありますが、実行速度は多少速くなっていますね。

Optional Chainingの使いすぎはバンドルサイズの肥大化に繋がることがわかりました。

### Optional Chainingの使い方を工夫する

``` typescript
const startTime3 = performance.now() // 開始時間
foo = { name: 'a', bar: { name: 'bar' } }
const d = foo.bar.baz?.name
console.log(d)
console.log(d)
console.log(d)
console.log(d)
console.log(d)
console.log(d)
console.log(d)
console.log(d)
console.log(d)
console.log(d)
const endTime3 = performance.now() // 終了時間
console.log('after2 *****************')
console.log(endTime3 - startTime3)
console.log('*****************')
```

バンドルサイズ
`11.09 KB (-338 B)  build/static/js/main.1e9f4693.chunk.js`

実行速度
`1.5050000001792796`

このやり方であればバンドルサイズを抑えつつ実行速度も速くなっています。


## まとめ
babelもprettierも対応していて環境としては十分整っているので、積極的に使っていくべきだとは思います。ただ、先にも説明したようにバンドルサイズは意味なく肥大化してしまう危険性があるので所構わず使っていいわけではなさそうです。用法用量を守って正しく使えば、バンドルサイズも抑えつつ人に優しいコードを書くことができそうです。