[TypeScript] 2.1¶
TypeScript2.1のリリース内容まとめ
keyof and Lookup Types¶
SHOULD NORMAL
keyof T
でTのプロパティからなるUnion Type
を表現できるT[K]
はlookup typesと呼ばれ、T
のプロパティK
と同様の型と判断される.
<T, K extends keyof T>
のような構文は頻出する.
stringのUnion typeはstringのsubtypeである.
同じく、UnionTypeT1
が別のUnionTypeT2
に完全に包含される場合、T1
はT2
の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 T
はT
の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.ts
とmain.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 ES2016
は ES2016のソースコードにコンパイルする ということ。
それゆえ、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 はその後に代入された値から判断する (--noImplicitAny がtrue の場合のみ) |
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
const
やreadonly
で初期化した変数の型推論が強化された。
型注釈を省略しても、リテラル型として判定される。
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.json
をextends
で継承できるようになった。
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
が推奨のため、使う機会は限られるはず