TypeScript3.1 リリース

Blog Single

少し遅くなってしまいましたが、9月28日にTypeScriptv3.1がリリースされました。
Angularがそれに合わせて、次のv7.0でこのv3.1までの対応を追加するようなので、先にTypeScriptの変更点を確認しておこうと思います。
ここではよく使われそうなものだけを抜粋してご紹介します。

関数内プロパティの定義

関数で合計値の計算をする際に、下記のように関数の外であらかじめ合計値のグローバル変数を定義したりしますよね。

let totalPrice: number = 0;

function calc(price: number): void {
    totalPrice = totalPrice + price;
    console.log(`現在の合計は${totalPrice}です。`);
}

calc(100); // 現在の合計は100です。
calc(100); // 現在の合計は200です。
calc(100); // 現在の合計は300です。
calc(100); // 現在の合計は400です。

もちろんこれでも処理が間違っているわけではありません。
ですが、このtotalPriceという変数を他で使用しない場合、グローバル環境に無駄に変数を定義するのは宜しくないと。
そういう時に関数内でのみ使用するプロパティを宣言できるようになりました。

calc.totalPrice = 0;

function calc(price: number): void {
    calc.totalPrice = calc.totalPrice + price;
    console.log(`現在の合計は${calc.totalPrice}です。`);
}

calc(100); // 現在の合計は100です。
calc(100); // 現在の合計は200です。
calc(100); // 現在の合計は300です。
calc(100); // 現在の合計は400です。

これであればグローバルを汚さなくて済みますね。
ただしここで注意が必要なのはcalc.totalPriceに型を定義できないことです。
型定義が命でもあるTypeScriptだと個人的には以下のようにしたくなります…

calc.totalPrice: number = 0; // ERROR

また、プロパティだけでなくメソッドとして定義することももちろんできます。
試しに書いたDEMOを置いておきます。
関数内メソッド定義DEMO

実はこの関数内プロパティの定義、JavaScriptを使用している方は知っていると思いますが、JavaScriptではもともと出来ます。
なんでもオブジェクトとして捉えてしまうJavaScriptらしい動きです。
それがTypeScriptでもできるようになったということですね。

Mapped Typeのマッピング挙動変更

PartialRequiredのような、既存の型を基にマップして新しい型を作るMapped Typeというものがあります。
例えばPromiseを使用したMapped Typeは以下のように定義できます。

type MapToPromise<T> = { [K in keyof T]: Promise<T[K]> };

このTに配列やタプルが代入されると、従来ではArrayに定義されているlength, pop, push, concatなどのメソッド名でマッピングされていましたが、v3.1ではnumberでマッピングされるようになりました。
内部的に定義が変更になっただけなので、コードの記述の仕方について変更ないようです。

type MapToPromise<T> = { [K in keyof T]: Promise<T[K]> };

// v3.0以前
type PromiseArray = MapToPromise<any[]>;
/* =>
    type PromiseArray = {
        [x: number]: Promise<any>;
        length: Promise<number>;
        toString: Promise<() => string>;
        toLocaleString: Promise<() => string>;
        pop: Promise<() => any>;
        push: Promise<(...items: any[]) => number>;
        concat: Promise<...>;
        ... 23 more ...;
        includes: Promise<...>;
    }
*/

// v3.1以降
type PromiseArray = MapToPromise<any[]>; // type PromiseArray = Promise<any>[]

BreakingChange

破壊的変更で注意しておきたい点は、Tが内部的にFunctionを含むよう拡張されたことです。
これにより、関数とobjectとTなどのジェネリクス型の区別が曖昧になり、下記のような判定で関数が呼び出せなくなっています。
以下の呼び出し方をしたい場合はより限定されるように型を指定する必要があるようです。
拡張されすぎても困りものですね…

// (公式引用)
function foo<T>(x: T | (() => string)) {
    if (typeof x === "function") {
        x(); // v3.1以降はERROR => Cannot invoke an expression whose type lacks a call signature. Type '(() => string) | (T & Function)' has no compatible call signatures.
    }
}

最後に

個人的に関数内プロパティが定義できるようになったのはありがたいですね。
プロパティやメソッドの位置関係が把握しやすいように思います。
Mapped Typeは普段あまり使用したことがなかったので、しっかり調べるきっかけになりました。
せっかくなのでこれから活用していこうと思います。

参考

Posted by Mao Miyaji
千葉にある夢の国を愛して止まない、元「魚のお姉さん」のエンジニア。PHP, TypeScriptメインで、暇さえあれば色々な言語を一かじり。

Other Posts: