水無瀬のプログラミング日記

プログラムの勉強した時のメモ

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でどうなってるか気にかけるきっかけになってくれたら幸いです。

参考サイト