TypeScriptのトランスパイル結果を意識する
はじめに
JavaScriptはTypeScriptではないけど、
TypeScriptはJavaScriptなのでそのことをアピールしたい今日この頃。
例えば下記のようなTSのコード。
const promiseFunction = (): Promise<string> => Promise.resolve('OK'); const asyncFunction = async () => console.log(await promiseFunction()); asyncFunction();
JSにトランスパイルされるとどうなるか気にしたことない人も多いかもしれない。
ちなみにこれは下記のようになる。
"use strict"; const promiseFunction = () => Promise.resolve('OK'); const asyncFunction = async () => console.log(await promiseFunction()); asyncFunction();
実質何も変わっていないが、
JSでできる書き方しかしていないので当たり前といえば当たり前。
ただし、これをasync/awaitが無いes6向けにトランスパイルするとこうなる。
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; const promiseFunction = () => Promise.resolve('OK'); const asyncFunction = () => __awaiter(void 0, void 0, void 0, function* () { return console.log(yield promiseFunction()); }); asyncFunction();
適当に使う分には複雑になるなーくらいで良いが、
ちゃんとしようと思うとバンドルサイズが増えたりとか、
パフォーマンスが劣化するとか言った問題がある。
TypeScript使ってるから大丈夫と慢心しないで、
どうトランスパイルされるかを意識したいのでまとめていく。
余談1
JavaScriptでasync/awaitが使えるのはes2017から。
余談2
es6向けではPromiseが使えるのでまだマシな見た目をしている。
es5まで落とすともう何書いてあるかわからない。
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var promiseFunction = function () { return Promise.resolve('OK'); }; var asyncFunction = function () { return __awaiter(void 0, void 0, void 0, function () { var _a, _b; return __generator(this, function (_c) { switch (_c.label) { case 0: _b = (_a = console).log; return [4 /*yield*/, promiseFunction()]; case 1: return [2 /*return*/, _b.apply(_a, [_c.sent()])]; } }); }); }; asyncFunction();
type annotation / interface / type alias
全部TypeScriptの機能。
(interfaceは厳密に言うと違うが)ざっくり全部型を付けてくれるもの。
ただし、TypeScriptの型は実行時に保証するものではないので、
全部トランスパイルすると消える。
実行時に保証しないのはとても重要でこれに苦しめられることも多々ある。
e.g. textboxにnumber入れたと思ってたら、実態がstringだった
実行時にinterfaceを用いて型チェック(instanceof IFoo
とか動かない)
TypeScript
const hoge: string = 'hoge'; const huga: number = 100; const isPiyo: boolean = true; interface IFoo { key: string; value: string; } const foo: IFoo = { key: 'keyだよ', value: 'valueだよ' } type Bar = { id: number; name: string; } const bar: Bar = { id: 1, name: 'hoge' }
JavaScript
"use strict"; const hoge = 'hoge'; const huga = 100; const isPiyo = true; const foo = { key: 'keyだよ', value: 'valueだよ' }; const bar = { id: 1, name: 'hoge' };
Enum
列挙型。
他の言語にあるものと大体同じだと思うが、一番の欠点はJSにないこと。
JSに無いということはトランスパイル結果がそのままにならないということ。
TypeScript
enum Sample { HOGE, HUGA, PIYO }; // デフォルトだと実態は数値になる console.log(Sample.HOGE); // 0 console.log(Sample.HUGA); // 1 console.log(Sample.PIYO); // 2 // デフォルト値を指定すればその値になる enum DefaultSample { HOGE = 'hoge', HUGA = 'huga', PIYO = 'piyo' } console.log(DefaultSample.HOGE); // hoge console.log(DefaultSample.HUGA); // huga console.log(DefaultSample.PIYO); // piyo // enumをconstにすると展開されビルド結果には残らなくなる const enum ConstSample { HOGE = 'hoge', HUGA = 'huga', PIYO = 'piyo' } console.log(ConstSample.HOGE); console.log(ConstSample.HUGA); console.log(ConstSample.PIYO);
JavaScript
"use strict"; var Sample; (function (Sample) { Sample[Sample["HOGE"] = 0] = "HOGE"; Sample[Sample["HUGA"] = 1] = "HUGA"; Sample[Sample["PIYO"] = 2] = "PIYO"; })(Sample || (Sample = {})); ; // デフォルトだと実態は数値になる console.log(Sample.HOGE); // 0 console.log(Sample.HUGA); // 1 console.log(Sample.PIYO); // 2 // デフォルト値を指定すればその値になる var DefaultSample; (function (DefaultSample) { DefaultSample["HOGE"] = "hoge"; DefaultSample["HUGA"] = "huga"; DefaultSample["PIYO"] = "piyo"; })(DefaultSample || (DefaultSample = {})); console.log(DefaultSample.HOGE); // hoge console.log(DefaultSample.HUGA); // huga console.log(DefaultSample.PIYO); // piyo console.log("hoge" /* HOGE */); console.log("huga" /* HUGA */); console.log("piyo" /* PIYO */);
Optional Chaining / Nullish Coalescing
最近TypeScriptに追加された機能。
どちらも今のJavaScriptに無い。つまり(ry
ちなみにOptional ChainingはStage4でNullish CoalescingはStage3。
※Stage4: 取り込み待ち(Finished)。Stage3: 仕様確定(Candidate)
TypeScript
// Optional Chaining type Foo = { id: number; ex?: { value: string; } } const foo1: Foo = { id: 1, ex: { value: 'hoge' } }; const foo2: Foo = { id: 2 } console.log(foo1.ex?.value); // hoge console.log(foo2.ex?.value); // undefined // Nullish Coalescing type Hoge = { id: number, name: string | null } const hoge1: Hoge = { id: 3, name: 'hoge name' } const hoge2: Hoge = { id: 4, name: null }; console.log(hoge1.name ?? 'name is null.'); // hoge name console.log(hoge2.name ?? 'name is null.'); // name is null
JavaScript
"use strict"; var _a, _b, _c, _d; const foo1 = { id: 1, ex: { value: 'hoge' } }; const foo2 = { id: 2 }; console.log((_a = foo1.ex) === null || _a === void 0 ? void 0 : _a.value); // hoge console.log((_b = foo2.ex) === null || _b === void 0 ? void 0 : _b.value); // undefined const hoge1 = { id: 3, name: 'hoge name' }; const hoge2 = { id: 4, name: null }; console.log((_c = hoge1.name, (_c !== null && _c !== void 0 ? _c : 'name is null.'))); console.log((_d = hoge2.name, (_d !== null && _d !== void 0 ? _d : 'name is null.')));
まとめ
最近ではJavaScript使う場面ではTypeScriptを使うことがデファクトになってきていると思う。
そのためJavaScriptを触ったことがない人も多いと思う。(自分もその一人)
ただ、TypeScriptはJavaScriptに取って代わる新言語ではないので、
結局はJavaScriptを知らないと苦しむことになる。
TypeScriptのトランスパイル結果を意識しない人もいるかもしれないですが、
間違った使い方をしないためにも、
少しでもJSでどうなってるか気にかけるきっかけになってくれたら幸いです。