About me
- フロントエンドエンジニア的なことをして生きています。
- TypeScript + AngularでSPA作ってます。
2.1 RC is coming!
2016.11.08現在、2.1 RCがリリースされた。
TypeScript blogによると、
- async/awaitのDown transpileがES5/ES3でも利用可能に(~2.0ではES2015以上をtargetにしないと使えなかった)
- 型推論がより賢くなった
と書いてある。
2.1.1にどのような変更が入ったかは、 @vvakame先生の記事 に詳しく記載されてる
2.1.4
2.1のRoadmap を読むと、
- Static types for dynamically named properties
- Mapped types
というのが載っている。今日の本題はこいつら。
Static types for dynamically named properties
https://github.com/Microsoft/TypeScript/pull/11929
feature 名長い。。。
そして「動的に命名されたプロパティのための静的型」とか、最早何のこっちゃ感。
このfeatureでは、keyof
という新しいキーワードと、T[K]
という2つの記法が追加された。
keyof
keyof T
で「type Tのプロパティ名の直和型」を表現するtypeが記述できる
interface User {
name: string;
age: number;
}
type UserKey = keyof User;
と書くと、 UserKey
は 'name' | 'age'
というtypeになるよ、ということ。
T[K]
T[K]
で「T
に対してK型でアクセスして得られるtype」を表現できる
K
は、keyof T
を満たすtypeであれば良いので、例えば
type UserName = User['name'];
とすると、UserName
のtypeは string
になる。 User['age']
とすれば number
。
利用例
interface Observable<T> {
pluck<K extends keyof T>(key: K): Observable<T[K]>;
}
let user$: Observable<User>;
let name$ = user$.pluck('name'); // Observable<string>
以前は name$ = user$.pluck<string>('name')
のように、動的にアクセスした先の型情報を自分で補う必要があった。
Mapped types
https://github.com/Microsoft/TypeScript/pull/12114
言ってしまえば**(半)動的にtypeを生成する機能**。
{[P in K]: T}
という記法が可能に。K
は stringにアサイン可能なtypeであればよく、 K
に対する値は T
というtypeになるよ、と言う意味。
type Item = { a: string, b: number, c: boolean };
type T1 = { [P in "x" | "y"]: number }; // { x: number, y: number }
type T2 = { [P in "x" | "y"]: P }; // { x: "x", y: "y" }
type T3 = { [P in "a" | "b"]: Item[P] }; // { a: string, b: number }
type T4 = { [P in keyof Item]: Date }; // { a: Date, b: Date, c: Date }
type T5 = { [P in keyof Item]: Item[P] }; // { a: string, b: number, c: boolean }
type T6 = { readonly [P in keyof Item]: Item[P] }; // { readonly a: string, readonly b: number, readonly c: boolean }
type T7 = { [P in keyof Item]: Array<Item[P]> }; // { a: string[], b: number[], c: boolean[] }
Mapped typesにより、既存typeのkey情報を再利用しつつ、新しいtypeを定義することができる:
type Freeze<T> = {
readonly [P in keyof T]: T[P];
};
let frozenUser: Freeze<User>;
frozenUser.age = 18; // error TS2540: Cannot assign to 'age' because it is a constant or a read-only property.
利用例1
オブジェクトの各key & valueに対して処理を行う関数の定義:
declare function mapObject<K extends string, T, U>(
obj: {[P in K]: T},
fn: (x: T, k?: K) => U
): {[P in K]: U};
const nameLengths= mapObject({
firstName: 'Yosuke',
lastName: 'Kurami'
}, (x => x.length));
// type of nameLengths: { firstName: number, lastName: number};
なお、type Record<K extends string, T> = {[P in K]: T}
というtypeがlib.es6.d.tsに定義済み.
利用例2
Boxingのサンプル(Angular2のReactive FormGroupを題材にしました)
interface FormControl<S> {
setValue(value: S): void;
reset(): void;
valueChanges: Observable<S>;
/* etc... */
}
type FormControls<T, K extends keyof T> = {[P in K]: FormControl<T[P]>};
interface FormGroup<T, K extends keyof T> {
controls: FormControls<T, K>;
set(v: Partial<T>): void;
};
declare var formBuilder: {
group<T, K extends keyof T>(obj: T): FormGroup<T, K>;
}
const user: User = {
name: 'Quramy',
age: 32,
};
const formGroup = formBuilder.group(user);
controls.name.valueChanges.subscribe(x => {/** ... */});
name
をboxingした FormControl(controls.name)は、name
の型に従って、Observable<string>
が返ってくる
まとめ
- TypeScript 2.1.4では、動的にプロパティを扱う関数の型定義が書きやすくなった
- コレクションやオブジェクトを操作する関数・ライブラリをより賢く記述できるようになった
- Immutable.jsやRx、lodash 等がより改善される...かも?
おまけ
TypeScript 2.1ではLanguage Service関連の機能追加(quickfix, implementation jump等)も含まれるので、
エディタプラグイン屋さんにも嬉しい!
2016.12.08追記
2.1.4(2.1系安定版) のリリース案内 https://blogs.msdn.microsoft.com/typescript/2016/12/07/announcing-typescript-2-1/ では、keyofやMapped Typesにも触れられています。