コンテンツにスキップ

[TypeScript] 3.6

TypeScript3.6のリリース内容まとめ

Stricter Generators

SHOULD NORMAL

ジェネレータ関数がIterableIteratorではなくGeneratorを返すようになった。
それによってジェネレータの型判定が厳密になった。

ジェネレータの返す値がyieldかreturnかを判別できるようになった

今まではnext(...)の結果が以下どちらか判別できなかった。

  • yieldによって返された値
  • returnによって返された値

v3.6からは判別できるようになった。

具体例

以下のコードはr.done === trueの結果によってit.next().valueの返却型は絞りこめる。

function* countDown() {
  yield 3;
  yield 2;
  yield 1;
  return "Go";
}

const it = countDown();
while (true) {
  const r = it.next();
  if (r.done) {
    console.log(r.value);
    break;
  } else {
    console.log(r.value);
  }
}

TypeScript3.5ではr.doneの結果は考慮されないが、TypeScript3.6からは考慮して推論される。

const it = countDown();
while (true) {
  const r = it.next();
  if (r.done) {
      // r.valueは "Go" | 3 | 2 | 1 と推論される
    console.log(r.value);
    break;
  } else {
      // r.valueは "Go" | 3 | 2 | 1 と推論される
    console.log(r.value);
  }
}
const it = countDown();
while (true) {
  const r = it.next();
  if (r.done) {
      // r.valueは string と推論される
    console.log(r.value);
    break;
  } else {
      // r.valueは 3 | 2 | 1 と推論される
    console.log(r.value);
  }
}

詳細

v3.6ではnext(...)IteratorResultを返すようになった。
これはyieldまたはreturnの返却値を意味する。

type IteratorResult<T, TReturn = any> =
  | IteratorYieldResult<T>
  | IteratorReturnResult<TReturn>;

interface IteratorYieldResult<TYield> {
  done?: false;
  value: TYield;
}

interface IteratorReturnResult<TReturn> {
  done: true;
  value: TReturn;
}

IteratorResultTagged union typesを使っているため、next(...).doneの結果によってnext(...).valueの型が判別できる。

yieldの戻り値型を考慮できるようになった (anyではなくなった)

v3.6では.next(...)の引数に適さない型を入れると、ちゃんとエラーになる。
v3.5ではすべての型を受けつけていた。

具体例

以下はnextで渡した引数を繋げて出力していくコード。
出力はsay saytake saytakehundredとなる。

function* stringPrinter() {
  let result = "";
  while (true) {
    const received: string = yield result;
    result += received.toLowerCase();
  }
}

const it = stringPrinter();
console.log(
  it.next().value,
  it.next("Say").value,
  it.next("TAKE").value,
  it.next("hundred").value
);

最後のnextに数値を入れてみる。
v3.5ではエラーにならないが、numbertoLowerCaseはないため実行時に失敗する。

function* stringPrinter() {
  let result = "";
  while (true) {
    const received: string = yield result;
    result += received.toLowerCase();
  }
}

const it = stringPrinter();
console.log(
  it.next().value,
  it.next("Say").value,
  it.next("TAKE").value,
  // Uncaught TypeError: received.toLowerCase is not a function
  it.next(100).value
);

詳細

v3.5で実行前エラーにならないのはnextの引数がanyになっているため。

interface Iterator<T> {
    next(value?: any): IteratorResult<T>;
    return?(value?: any): IteratorResult<T>;
    throw?(e?: any): IteratorResult<T>;
}

v3.6ではGenerator型に対応しており、nextが返すTNextの型と比較できるようになった。

interface Generator<T = unknown, TReturn = any, TNext = unknown> extends Iterator<T, TReturn, TNext> {
    // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
    next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
    return(value: TReturn): IteratorResult<T, TReturn>;
    throw(e: any): IteratorResult<T, TReturn>;
    [Symbol.iterator](): Generator<T, TReturn, TNext>;
}

これによって型エラーが検知される。

型 '100' を型 'string' に割り当てることはできません。ts(2345)

More Accurate Array Spread

NOT NECESSARY NORMAL

ArrayのSpreadがより正確になった。

[...Array(5)][undefined, undefined, undefined, undefined, undefined]を期待するが、--downlevelIterationなしだとv3.5ではArray(5).slice()にトランスパイルされていた。

TypeScriptバージョン --downlevelIteration [...Array(5)] のトランスパイル結果
3.5 true __spread(Array(5))
3.5 false Array(5).slice()
3.6 true __spread(Array(5))
3.6 false __spreadArrays(Array(5))

v3.6からは[undefined, undefined, undefined, undefined, undefined]を返すようになる。

Improved UX Around Promises

NOT NECESSARY EASY

Promiseに関するエラーが分かりやすくなった。

declare function createNumberPromise(): Promise<number>;
declare function showNumber(value: number): void;

async function f() {
  showNumber(createNumberPromise());
}

上記はPromiseを解決せず関数の引数に渡してしまった例。
v3.5は真実のみを伝えているが、v3.6ではawait忘れではないかと提案してくれる。

index.ts:5:14 - error TS2345: Argument of type 'Promise<number>' is not assignable to parameter of type 'number'.

5   showNumber(createNumberPromise());
index.ts:5:14 - error TS2345: Argument of type 'Promise<number>' is not assignable to parameter of type 'number'.

5   showNumber(createNumberPromise());
~~~~~~~~~~~~~~~~~~~~~

index.ts:5:14
5   showNumber(createNumberPromise());
~~~~~~~~~~~~~~~~~~~~~
Did you forget to use 'await'?

Promise<T>をawaitしないままTに存在するプロパティを呼び出したときも同様の提案をしてくれる。
...がこれはv3.5の時点から対応されていた。(リリースノートの間違い?)

Better Unicode Support for Identifiers

NOT NECESSARY EASY

targetにes2015以降を指定すると、Unicodeがよりサポートされるようになった。

import.meta Support in SystemJS

UNKNOWN EASY

moduleターゲットがsystemのときimport.metacontext.metaに変換するようになった。

何が嬉しいかがわからない..

get and set Accessors Are Allowed in Ambient Contexts

HAD BETTER EASY

declareを使ったクラスや.d.tsの型定義でgetterやsetterを書けるようになった。

// v3.5だとエラーになる
declare class Human {
  get name(): string;
  set age(value: number);
}

Ambient Classes and Functions Can Merge

HAD BETTER EASY

declareを使ったClassとFunctionの宣言をマージできるようになった。
v3.5では識別子重複エラーになる以下のようなコードが書ける。

export declare function Human(id: number, name: string): Human;
export declare class Human {
  id: number;
  name: string;
  constructor(id: number, name: string);
}

これはCallable constructor patternを実現できており、newなしでインスタンス生成させたい場合に便利である。

const jiro = Human(1, "jiro");

APIs to Support --build and --incremental

NOT NECESSARY EASY

--build1--incremental2オプションに相当するコンパイラAPIがサポートされるようになった。
GulpやWebpackなど3rd partyのビルドツールでもこれらの恩恵を得られるようになる。

Semicolon-Aware Code Edits

HAD BETTER EASY

IDEでquick fixやrefactoringを実行したとき、セミコロンを付けるかどうかの挙動が変わった。

バージョン セミコロン
v3.5 必ずセミコロンを付ける
v3.6 対象ファイルのセミコロン利用状況を見て判断する

セミコロンを使用しないスタイルの人にとってはPrettierなどで変更する手間が省ける。

Smarter Auto-Import Syntax

HAD BETTER EASY

IDEでAuto-importを実行したとき、どの形式でimport文を挿入するかの挙動が変わった。

バージョン セミコロン
v3.5 ECMAScript module syntaxに従う
v3.6 対象ファイルのimport状況を見て判断する

ECMAScript module syntaxでimportしていないプロジェクトは変更の手間が省ける。

New TypeScript Playground

HAD BETTER EASY

TypeScript Playgroundが新しくなった。

  • targetが指定できるようになった
  • strict系フラグ
  • JavaScript対応 (allowJS, checkJs)