コンテンツにスキップ

[TypeScript] 2.1

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

keyof and Lookup Types

SHOULD NORMAL

  • keyof TTのプロパティからなるUnion Typeを表現できる
  • T[K]はlookup typesと呼ばれ、TのプロパティKと同様の型と判断される.

<T, K extends keyof T>のような構文は頻出する.

stringのUnion typeはstringのsubtypeである.
同じく、UnionTypeT1が別のUnionTypeT2に完全に包含される場合、T1T2のsubtypeでありT1 extends T2である.

Tを以下のように定義したとき

class T {
  p1: string
  p2: number
}
  • keyof T"p1" | "p2"である
  • "p1""p1" | "p2"のsubtypeである
  • "p2""p1" | "p2"のsubtypeである

それゆえ

<T, "p1"> または <T, "p2"> => <T, K extends "p1" | "p2"> => <T, K extends keyof T>

といえる.

Mapped Types

HAD BETTER NORMAL

[P in keyof T]と書くと、ある型のプロパティを利用して型を定義できる.

たとえば、ある型Tに対して、すべてのプロパティをOptionalにする型を以下のように作成できる.

type Partial<T> = {
  [P in keyof T]?: T[P]
}

Tがinterface Human { id: number, name: string }のとき、Pythonのような疑似表記を交えて書くと..

keyof TTのUnion Typeなので

type Partial<Human> = {
  for P in ['id', 'name']:
    P?: Human[P]
}

↓ for文を回すと..

type Partial<Human> = {
  id?: Human['id']
  name?: Human['name']
}

↓ Lookup Typesより..

type Partial<Human> = {
  id?: number
  name?: string
}

となる.

Partial, Readonly, Record, and Pick

HAD BETTER NORMAL

意味
Partial TのプロパティがすべてOptionalになった型
Readonly Tのプロパティがすべて読みこみ専用になった型
Pick TのプロパティからKのプロパティのみを残す
Record Kのプロパティ名を持ち、その型全てがTとなる型

Humanを使った例

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

それぞれ以下のようになる。

type Partial<Human> = {
  id?: number;
  name?: string;
  age?: number;
}

type Readonly<Human> = {
  readonly id: number;
  readonly name: string;
  readonly age?: number;
}

type Pick<Human, "id" | "name"> = {
  id: number;
  name: string;
}

type Record<keyof Human, number> = {
  id: number;
  name: number;
  age?: number;
}

Pickの補足

Pick<T, K>型を返すpick(...)について、TがHumanのときを考える。

function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>;

↓ T = Humanより

function pick<Human, K extends "id" | "name" | "age">(obj: Human, ...keys: K[]): Pick<Human, K>

K extends "id" | "name" | "age"は以下のいずれか。

  • "id"
  • "name"
  • "age"
  • "id" | "name"
  • "id" | "age"
  • "name" | "age"
  • "id" | "name" | "age"

ここで、K = "id" | "name"のケースを考えると..

function pick<Human, "id" | "name">(obj: Human, ...keys: ("id" | "name")[]): Pick<Human, "id" | "name">

↓ Rest Parameterを展開すると...

function pick<Human, "id" | "name">(obj: Human, id: "id", name: "name")[]): Pick<Human, "id" | "name">

よって以下は成立する。

pick({id: 1, name: 'ichiro', age: 11}, "id", "name")
// -> {id: 1, name: 'ichiro'}

Object Spread and Rest

SHOULD EASY

...をオブジェクトの前に付けると展開される。
...{x: 1, y: 2}x: 1, y: 2のイメージ。

Shallow copyとして使う

const obj = { id: 1, name: "Ichiro" };
const copied = { ...obj };

console.log(obj);
// -> { id: 1, name: 'Ichiro' }
console.log(copied);
// -> { id: 1, name: 'Ichiro' }
console.log(obj === copied);
// -> false

Objectのマージに使う

あるものは上書き、無いものは追加される。

const ichiro = { id: 1, name: "Ichiro" };
const nanashi = { id: 2, favorite: "Japan" };

console.log({ ...ichiro, ...nanashi });
// -> { id: 2, name: 'Ichiro', favorite: 'Japan' }
console.log({ ...nanashi, ...ichiro });
// -> { id: 1, favorite: 'Japan', name: 'Ichiro' }

Restとして使う

restは{id: number, favorite: string}型と判断される。

function main() {
  const ichiro = { id: 1, name: "Ichiro", favorite: "Japan" };
  const { name, ...rest } = ichiro;
  console.log(name);
  // -> Ichiro
  console.log(rest);
  // -> { id: 1, favorite: 'Japan' }
}

main();

Downlevel Async Functions

NOT NECESSARY EASY

TargetがES3/ES5でもasync functionが使えるようになった。

Promiseは必要です

Support for external helpers library (tslib)

HAD BETTER EASY

--importHelpersオプションを付けると、__extends__assign__awaiterなどのヘルプ関数をtslibから読み込まれるようになる。
以下のようなメリットがある。

  • ファイルサイズが削減する
    • 全てのjavascriptファイルに上記関数の実装が埋め込まれなくなるため
  • 上記ファイルサイズ削減のために独自Helperライブラリ管理をしていた場合、解放される

たとえば、以下のsub.tsmain.tsがあったとき。

sub.ts

export const o = { a: 1, name: "o" };
export const copy = { ...o };

main.ts

import * as sub from "./sub";

export const o = { a: 1, name: "o" };
export const copy = { ...o };

--importHelpersの有無によって、ビルドされた結果のjsファイルは以下のように変わる。

"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.o = { a: 1, name: "o" };
exports.copy = __assign({}, exports.o);
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
exports.o = { a: 1, name: "o" };
exports.copy = tslib_1.__assign({}, exports.o);

Untyped imports

NOT NECESSARY EASY

--noImplicitAnyオプションが無効な場合に限り、型定義の無いJavaScriptモジュールがimportできるようになった。
たとえば以下のようなコードが動く。

import { sum } from "./sub";

console.log(sum(100, 1));
function sum(a, b) {
    return a + b
}
module.exports = { sum }

Support for --target ES2016, --target ES2017 and --target ESNext

NOT NECESSARY EASY

--targetに以下が追加された。

  • ES2016
  • ES2017
  • ESNext

--targetはコンパイル後のソース対応状況を示す。
つまり、--target ES2016ES2016のソースコードにコンパイルする ということ。
それゆえ、ES2017以降の仕様が出現した場合はES2016で動くように変換される。

Example

以下のindex.tsがあったとき..

function createPromise(): Promise<string> {
  return new Promise((resolve) => resolve("done"));
}

async function main() {
  const ret = await createPromise();
  console.log(ret);
}

main();

それぞれのターゲットで生成されるindex.jsは以下のようになる。

"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());
    });
};
function createPromise() {
    return new Promise((resolve) => resolve("done"));
}
function main() {
    return __awaiter(this, void 0, void 0, function* () {
        const ret = yield createPromise();
        console.log(ret);
    });
}
main();
"use strict";
function createPromise() {
    return new Promise((resolve) => resolve("done"));
}
async function main() {
    const ret = await createPromise();
    console.log(ret);
}
main();

Improved any Inference

NOT NECESSARY EASY

let xのように型が明示されていない変数の型推論が改善した.

改善前 改善後
x はany x はその後に代入された値から判断する (--noImplicitAnytrueの場合のみ)
const sum = (a: number, b: number): number => a + b;

let x;
x = "42";
// 2.1より前はanyと推論されOKだったが、2.1以上では"42"の代入からstringと推論されNG
sum(x, x);

Implicit any errors

SHOULD EASY

--noImplicitAnyが有効な場合、暗黙的なanyはエラーになる。
保守性を保つために必須。

型推論可能なものはエラーにならない

Better inference for literal types

NOT NECESSARY EASY

constreadonlyで初期化した変数の型推論が強化された。
型注釈を省略しても、リテラル型として判定される。

const hello = "hello"
// helloは "hello"型になる (string型にならない!)
const one = 1
// oneは 1型になる (number型にならない!)

constで初期化した変数を、letで宣言した別変数に代入するときは推論が変わるので注意。

const hello = "hello"
let tmp = hello
// tmpはstring型になる. letは変更を許容しているため"hello"のままとは限らないから
// const tmp = hello であれば"hello"型をキープできる

Use returned values from super calls as ‘this’

NOT NECESSARY EASY

super()がreturnする値を読み出し元(子クラス)のthisとして扱う。
以前は未対応だったため、super()の値を1度キャプチャしてからthisを置き換えていたらしい..

class Base {
  x: number;
  constructor() {
    return {x: 1};
  }
}

class Derived extends Base {
  constructor() {
    super(); // 呼び出し後にthisは{x: 1}になる
  }
}

Configuration inheritance

HAD BETTER EASY

tsconfig.jsonextendsで継承できるようになった。
extendsで指定したファイルの設定が先に読み込まれ、その後に自身の設定でOverrideする。

compilerOptionsの値はマージされるが、以下は上書きなので注意。

  • files
  • include
  • exclude

{
  "compilerOptions": {
    "target": "es3",
    "strict": true
  },
  "include": ["include-base.ts"]
}
{
  "extends": "./tsconfig-base",
  "compilerOptions": {
    "target": "es2015",
  },
  "include": ["include.ts"]
}
{
  "compilerOptions": {
    "target": "es2015",
    "strict": true
  },
  "include": ["include.ts"]
}

New --alwaysStrict

NOT NECESSARY EASY

--alwaysStrictオプション付きでコンパイルすると、strict modeのように振る舞う。

strict modeが有効かを確かめるには以下のポイントをチェックする。

  • 変数名に予約語(privateなど)を使った時コンパイルが通るか
  • ビルド後のjsファイルに"use strict"が付いているか

TypeScript2.3からは strict: trueが推奨のため、使う機会は限られるはず