コンテンツにスキップ

[TypeScript] 3.2

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

strictBindCallApply

HAD BETTER EASY

--strictBindCallApplyオプションが有効なとき、以下3つのメソッドの型チェックを厳密に行うようになった。
--strictが有効であれば--strictBindCallApplyを個別に指定する必要はない。

  • bind
  • call
  • apply
function foo(a: number, b: string): string {
  return a + b;
}

// 正しい
foo.apply(undefined, [10, "hello"]);
// -> 10hello

// requiredの第2引数が指定されていない (けどエラーにならない..!!)
foo.apply(undefined, [10]);
// -> NaN

// 第2引数の型が違う (けどエラーにならない..!!)
foo.apply(undefined, [10, 20]);
// -> 30

// 不要な第3引数が含まれている (けどエラーにならない..!!)
foo.apply(undefined, [10, "hello", 30]);
// -> 10hello
function foo(a: number, b: string): string {
  return a + b;
}

// 正しい
foo.apply(undefined, [10, "hello"]);
// -> 10hello

// requiredの第2引数が指定されていない (のでERRORになる)
foo.apply(undefined, [10]);
// index.ts:5:30 - error TS2345: Argument of type '[number]' is not assignable to parameter of type '[number, string]'.
//   Property '1' is missing in type '[number]' but required in type '[number, string]'.

// 第2引数の型が違う (のでERRORになる)
foo.apply(undefined, [10, 20]);
// index.ts:6:35 - error TS2322: Type 'number' is not assignable to type 'string'.

// 不要な第3引数が含まれている (のでERRORになる)
foo.apply(undefined, [10, "hello", 30]);
// index.ts:7:30 - error TS2345: Argument of type '[number, string, number]' is not assignable to parameter of type '[number, string]'.
//  Types of property 'length' are incompatible.
//    Type '3' is not assignable to type '2'.

Caveats

HAD BETTER EASY

strictBindCallApplyは以前エラーでなかった箇所がエラーになるため、破壊的変更と言える。
また、bind call apply は以下のケースで完全に正しくモデル化できないので注意。

  • ジェネリックな関数
  • オーバーロードされた関数

Generic spread expressions in object literals

HAD BETTER EASY

ジェネリクス型でもspread構文が使えるようになった。

エラーになります。

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

function mergeWithNumber<T>(obj: T, num: number) {
  return { ...obj, number: num }; // error TS2698: Spread types may only be created from object types.
}

const taro: Human = { id: 1, name: "taro" };
const jiro = mergeWithNumber(taro, 77);

利用可能で型推論もできます。

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

function mergeWithNumber<T>(obj: T, num: number) {
  return { ...obj, number: num };
}

const taro: Human = { id: 1, name: "taro" };
const jiro = mergeWithNumber(taro, 77);
// taroの型 と number の Intersection typesになる
// jiro: Human & {
//    number: number;
// }

Generic object rest variables and parameters

HAD BETTER EASY

ジェネリクス型でもobjectのrest parametersが使えるようになった。

エラーになります。

interface Human {
  id: number;
  name: string;
  isGod: boolean;
}

function showWithoutId<T extends { id: number }>(obj: T) {
  const { id, ...rest } = obj; // error TS2698: Spread types may only be created from object types.
  console.log(rest);
}

const kbys: Human = { id: 1, name: "yshr", isGod: true };
showWithoutId(kbys);

利用可能で型推論もできます。

interface Human {
  id: number;
  name: string;
  isGod: boolean;
}

function showWithoutId<T extends { id: number }>(obj: T) {
  const { id, ...rest } = obj;
  console.log(rest); // rest: Pick<T, Exclude<keyof T, "id">> と推論
}

const kbys: Human = { id: 1, name: "yshr", isGod: true };
showWithoutId(kbys);
// -> { name: 'yshr', isGod: true }

BigInt

NOT NECESSARY EASY

numberよりも大きな数で精度が保てる型bigintが追加された。

// number
console.log(12345678901234567890);
// -> 12345678901234567000
// 最後の890で精度が保たれていない

// bigint (末尾にnをつけるか BigInt(...)で作る)
console.log(12345678901234567890n);
// -> 12345678901234567890n
// 精度が保たれる

numberbigintは相互に代入/計算できないためキャストが必要。
また、利用にはtarget: esnextが必要。 (ES2020以降)

Non-unit types as union discriminants

HAD BETTER EASY

Union typesの各typeがシングルトン(固定文字列のtagなど)以外のプロパティを持つ場合でもnarrowingができるようになった。

type Result<T> = { error: Error; data: null } | { error: null; data: T };

function unwrap<T>(result: Result<T>) {
  if (result.error) {
    // 3.1も3.2以降も `Error`型
    throw result.error;
  }

  // 3.1以前は `T | null`型
  // 3.2以降は `T`型
  return result.data;
}

result.errorTまたはnull型だが、Tはシングルトンでないため以前はnarrowingできなかった。
v3.2からはそれが可能になったため、if (result.error)の条件ブロックに入らなかった時点でResult<T>型は{ error: null; data: T }型であると判断できるようになった。

tsconfig.json inheritance via Node.js packages

HAD BETTER EASY

node_modulesからtsconfig.jsonを継承できるようになった。
オレオレtsconfig.jsonをpackage化してextendsに指定できる。

extendsConfiguration inheritance を参照

The new --showConfig flag

HAD BETTER EASY

tscコマンドに--showConfigが追加された。
このオプションをつけると、コマンド実行時に有効と判定されたtsconfig.jsonの設定が出力される。

$ npx tsc --showConfig
{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
    },
    "files": [
        "./main.ts"
    ]
}

特にextendsを使った時は便利。

{
  "compilerOptions": {
    "target": "esnext"
  },
  "extends": "./parent-tsconfig.json"
}
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true
  }
}

targetだけが上書き/マージされた結果を取得できる。

$ npx tsc --showConfig
{
    "compilerOptions": {
        "target": "esnext",
        "module": "commonjs",
        "strict": true
    },
    "files": [
        "./main.ts"
    ]
}

Object.defineProperty declarations in JavaScript

NOT NECESSARY EASY

JavaScriptファイルで// @ts-checkを使用したとき、Object.definePropertyの処理が考慮されるようになった。

--allowJsを有効にした上で以下のファイルを作成してみる。

// @ts-check

let ichiro = {};
Object.defineProperty(ichiro, "name", { value: "Ichiro" });

ichiro.name.toLowerCase();
// -> ichiro を期待

v3.1以前とv3.2では以下のように結果が異なる。

ichironameプロパティがあると認識されない。

ichironameプロパティがあると認識される。