コンテンツにスキップ

[TypeScript] 2.7

TypeScript2.7のリリース内容まとめ

Constant-named properties

HAD BETTER EASY

Symbolを含む、constで宣言された変数のプロパティ名に対応した。

const hoge = Symbol("hogehoge");

const obj = {
    [hoge]: "value"
}

// obj.で補完候補が出現し、obj[hoge]はstringと推論される
const str = obj[hoge]

stringやnumberの場合も同様だが、以前は対応していなかったのかは謎。。

unique symbol

HAD BETTER NORMAL

symbolのサブタイプとしてunique symobl型が追加された。
一意性が保証されており、代入や比較ができないという特徴がある。

利用できるシーン/できないシーンは以下の通り。

シーン unique symbol? コード例
constで明示的に宣言 unique symbol declare const x: unique symbol;
constSymbol()を代入 unique symbol const x = Symbol();
明示的にSymbol()を代入 unique symbol let x: unique symbol = Symbol();
明示的にSymbol.for()を代入 unique symbol let x: unique symbol = Symbol.for("key");
static readonlyのプロパティにSymbol()を代入 unique symbol class Inf { static readonly x = Symbol() }
constSymbol.for()を代入 symbol const x = Symbol.for("key");
static readonlyのプロパティにSymbol.for("key")を代入 symbol class Inf { static readonly x = Symbol.for("key") }
letで明示的に宣言 エラー declare const x: unique symbol;

利用例として、Nominal Typingを実現するために付与する一意なプロパティなどが考えられる。

class Dog {
    constructor(public name: string) { }
}

class Cat {
    constructor(public name: string) { }
}

function nyan(cat: Cat) {
    console.log(cat.name);
}

const dog = new Dog("pochi");

// 引数はCatだがdog(: Dog)はCatと同じプロパティを持つため動作する
nyan(dog);
const DogType = Symbol();
class Dog {
    private [DogType]: void;
    constructor(public name: string) { }
}

const CatType = Symbol();
class Cat {
    private [CatType]: void;
    constructor(public name: string) { }
}

function nyan(cat: Cat) {
    console.log(cat.name);
}

const dog = new Dog("pochi");

// プロパティが違うのでちゃんとエラーにしてくれる
// TS2345: Argument of type 'Dog' is not assignable to parameter of type 'Cat'.
// Property '[CatType]' is missing in type 'Dog' but required in type 'Cat'.
nyan(dog);

Strict Class Initialization

SHOULD EASY

--strictPropertyInitializationフラグが追加された。
有効にすると、クラスのコンストラクタで初期化されていないプロパティがエラーになる。

class Human {
    id: number;  // <--- これがエラーになる. number | undefined にするか初期化すべき
    name: string;

    constructor(name: string) {
        this.name = name
    }
}

Injectionライブラリなどで上記挙動が好ましくない場合、!を付けることで回避できる。

class Human {
    id!: number;  // これはエラーにならない
    name: string;

    constructor(name: string) {
        this.name = name
    }
}

--strictPropertyInitializationフラグは--strictモードであれば自動で有効になる。
--strictモードでも、--strictPropertyInitializationフラグを明示的にOFFにすると、このエラーチェックを外すことができる。

Definite Assignment Assertions

SHOULD EASY

プロパティ名や変数名のあとに!をつけると、コードから判別できなくても『値が代入されている』とみなせる。
別の言い方をするとnon-nullであることを保証できる。

function sum(x: number, y: number): number {
  return x + y;
}

const x = 1;
let y: number;

// `y` だと代入前の利用エラーになるが、`y!`だとOK
sum(x, y!)

Fixed Length Tuples

HAD BETTER EASY

要素の型が包含されている長さの違うTupleに代入できなくなった。

以前は[A, B][A, B, C]のサブタイプと見なしていたが、lengthプロパティの違いからそう見なさなくなった。

バージョン [A, B, C] <- [A, B] [A, B] <- [A, B, C]
v2.6以前 エラー OK
v2.7以降 エラー エラー
const child: [number, string, string] = [1, "one", "ichi"];
// 以前はOKだったがエラーになる
const parent: [number, string] = child;

Improved type inference for object literals

HAD BETTER EASY

オブジェクトリテラルの型推論が改善された。

const obj = test ? { text: "hello" } : {};

v2.6以前だとobj.textはエラーになったが、v2.7以降はエラーにならない。

Improved handling of structurally identical classes and instanceof expressions

HAD BETTER NORMAL

構造的に一致するクラスの判定とinstanceof式の挙動が改善された。
クラス判定を行うとき、構造的にsubtypeかどうかではなくクラスの継承関係を見るようになった。

以下の4クラスについて

class A {
    id!: number
}

class AChild extends A {
    name!: string
}

class AChild2 extends A {
    name!: string
}

class ALike {
    id!: number
}
v2.6以前の推論型 v2.7以降の推論型 備考
!true ? new A() : new AChild() A A 親子なら親へ
!true ? new AChild() : new AChild2() AChild AChild ┃ AChild2 親子関係はない
!true ? new Alike() : new A() ALike ALike ┃ A 親子関係はない

また、関数の中でinstanceofでTypeGuardを行うと..

function ff(a: AChild | AChild2) {
    if (a instanceof AChild) {
        // v2.6以前だと a は `AChild | AChild2`
        // v2.7以降だと a は `AChild`
    }
}

Type guards inferred from in operator

HAD BETTER EASY

Objectに特定プロパティを持つかチェックするin式が、Type Guardに対応した。

interface Human {
    myNo: number;
    name: string;
}

interface Dog {
    name: string
}

function getMyNo(entity: Human | Dog): number {
    if ("myNo" in entity) {
        // entityはHuman型として扱われる
        return entity.myNo;
    }

    // entityはDog型として扱われる
    throw new Error(`${entity.name} に マイナンバーはありません.`)
}

Support for import d from "cjs" from CommonJS modules with --esModuleInterop

--esModuleInteropフラグを使うとCommonJS moduleをimport d from "cjs"の形でインポートできるようになった。

  • import * as foo from "foo" はエラーになる
  • CommonJS/AMD/UMDからのデフォルトインポートが許可されるようになった

デフォルトエクスポートって最近では非推奨だったような気も。。🤔

Numeric separators

HAD BETTER EASY

数字をアンダースコアで区切れるようになった。
10進数以外を使う場合は分かりやすい。

  • 1_000_000
  • 1_0_0_0_0
  • 0xFF_AA_BB_3C
  • 0b0001_0010_0100_1000

Cleaner output in --watch mode

NOT NECESSARY EASY

--watchコマンド実行時、再コンパイルされたら画面がクリアされるようになった。

Prettier --pretty output

NOT NECESSARY EASY

エラーメッセージが見やすくなる。

※ 後のバージョンでデフォルト有効になるので気にしなくてもよい

main.ts(5,1): error TS2554: Expected 2 arguments, but got 0.

main.ts:5:1 - error TS2554: Expected 2 arguments, but got 0.

5 sum()
  ~~~~~

  main.ts:1:14
    1 function sum(x: number, y: number): number {
                   ~~~~~~~~~
    An argument for 'x' was not provided.


Found 1 error.

※ 実際は色がつく