TypeScriptの3.7.2
TypeScriptの新バージョンが11月にリリースされました。
Announcing TypeScript 3.7 | TypeScript
私自身、特に注目していたのが Optional Chaining
でした。
簡単に説明をすると下記のように書くことができます。
let x = foo?.bar.baz();
今までであればこんな書き方としていたものや
let x = (foo === null || foo === undefined) ?
undefined :
foo.bar.baz();
他にもこんな書き方をしていたものを簡潔に書くことができて、とても人に優しい書き方ができますね。
let x = foo && foo.bar.baz()
実際に私も携わっているプロジェクトで foo && foo.bar && foo.bar.hoge
というようにしなければいけないことが多く、この見にくいコードから開放されれば良いと 3.7
のリリースをまだかまだかと思っていました。そういった開発者の方は多いのではないかなと思います。
今回はReactを使ったプロジェクトのTypeScriptのバージョンをあげ環境を整える部分と、実際に使ってみた感想と注意点をご紹介できればと思います。
始める際の留意点
大体の場合下記の手順でできるはずです。
- tsのバージョンを上げる
- @babel/plugin-proposal-optional-chainingを導入
- VSCodeのワークスペースのTSバージョンを上げる
- prettierを最新バージョンにする
1と2はいいとして3、4は私もちょっとハマったとこなのでピックアップして説明しますね。すでに環境が整っている方はここは飛ばしてしまって良いです。
VSCodeのワークスペースのTSバージョンを上げる
VS Codeを使っている場合VS CodeのワークスペースのTSバージョンを変更しないとエラーになってしまいます。
エラーを解消するにはVS Codeの右下の方にある数字の部分をクリック(ここだと3.6.3)して 3.7.2
を選びます。
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 でコードを書きながら試すことができます。
例えばこんなコードがあるとして
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);
完全に勘違いしていた
私の知識不足なんですが、トランスパイルされたコードは普通にこうなるかと思っていました。(一緒に働いているエンジニアさんも勘違いしていたみたい)
foo.bar.baz && foo.bar.baz.name
ここでひとつ懸念が・・・
「これ、使いすぎるとバンドルサイズやばくね??」
「あれ?じゃあ、実行速度はどうなんよ??」
SPAプロジェクトにおいてバンドルサイズは大敵です。もちろんServiceWorkerでキャッシュさせて初回以降のRenderを速くすることは可能ですが、バンドルサイズが大きければ大きいほど初回renderは遅くなってしまいます。
ってことで簡単なテストですが、バンドルサイズと実行速度の検証をしてみました。
検証
実際のReactのプロジェクトにコードを書いてテストをしてみました。
Optional Chaining
を使わないパターン
単純に &&
で評価してくパターンです。正直可読性よくない。
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を使う
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の使い方を工夫する
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も対応していて環境としては十分整っているので、積極的に使っていくべきだとは思います。ただ、先にも説明したようにバンドルサイズは意味なく肥大化してしまう危険性があるので所構わず使っていいわけではなさそうです。用法用量を守って正しく使えば、バンドルサイズも抑えつつ人に優しいコードを書くことができそうです。