Help us understand the problem. What is going on with this article?

TypeScript 2.1のkeyofとかMapped typesがアツい

More than 3 years have passed since last update.

TypeScript 2.1のkeyofとかMapped typesがアツい

by Quramy
1 / 14

About me

@Quramy

  • フロントエンドエンジニア的なことをして生きています。
  • 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にも触れられています。

Quramy
Front-end web developer. TypeScript, Angular and Vim, weapon of choice.
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした