keyof
キーワード
key
とオブジェクトを受け取りプロパティの値を取り出す関数を考えます。
function getProp(obj: {}, key: string) { return obj[key]; }
この関数使って変数を宣言すると型推論では返り値は any
になってしまいます。
const urara = { age: 15, name: "chiya", }; const a = getProp(urara, 'age'); // any const n = getProp(urara, 'name'); // any
もうちょい頑張って型付けしたいと思ったら obj
の型を絞るしかなさそうです。
interface Urara { age: number; name: string; }
では key
の型は?となると今までは素朴に String Literal type で羅列するしかありませんでした。
type UraraKeys = 'age' | 'name';
ですが keyof
キーワードが追加されたことにより、 UraraKeys
の定義が少し楽になります。
type UraraKeys = keyof Urara;
めでたい。動作は想像通り key
の Union Type が返ります。
これらを使うと getProp()
はこんな感じになります。
function getProp(obj: Urara, key: UraraKeys) { return obj[key]; } interface Urara { age: number; name: string; } type UraraKeys = keyof Urara; const urara = { age: 15, name: "chiya", }; const a = getProp(urara, 'age'); // string | number const n = getProp(urara, 'name'); // string | number
Lookup Types
keyof
の登場によって多少マシになったとは言えまだイマイチです。具体的には
obj
の型を具体的にする必要がある- いちいち
key
の型を宣言しなくてはならない - 型推論では
string | number
になってしまう
微妙ですね。
ですがこれも TS2.1.4 では解決されており
function getProp<T, K extends keyof T>(obj: T, key: K) { return obj[key]; }
と書くと T[K]
が返るようになります。
これにより
const a = getProp(urara, 'age'); // number const n = getProp(urara, 'name'); // string
とキャストする必要がなくなります。めでたい。
実際使うの?
実例としては Object.entries()
(lib.es2017.object.d.ts) の定義で使われています。
Mapped Types
Object.freeze()
のラッパー関数を考えます。
interface Urara { age: number; name: string; } interface FrozenUrara { readonly age: number; readonly name: string; } function freezeUrara(u: Urara): FrozenUrara { return Object.freeze(u); } const fu = freezeUrara({ name: 'chiya', age: 15 }); fu.age = 12; // error
freezeUrara
は Urara
型を受け取って FrozenUrara
を返す関数です。
FrozenUrara
は Urara
の書くプロパティに readonly
を付けただけの型。
これ毎回2つインターフェースを用意するはだるいですよね。今までは必要だったのですがこれも TS2.1.4 では解決してます。
どんなふうにかというと既に Object.freeze()
自体がいい感じになっているのでそれを見ると良さそうです(なのでラッパーとか作らなくてよかった)。
freeze<T>(o: T): Readonly<T>;
Readonly
とはなにかと言うと定義はこんなふうになってます。
type Readonly<T> = { readonly [P in keyof T]: T[P]; };
in
キーワードが登場しました。
これは mapped type
を扱うためのキーワードでまぁ想像通り in
の後に置かれた Union Type から一つづつ取り出し map していきます。
この場合は keyof T
から一つづつ取り出し readonly
の付いた T[P]
を返します。これでいちいちインターフェースを2つ作る必要がなくなりました。
Readonly
みたいな型は他にも定義されていて
/** * Make all properties in T optional */ type Partial<T> = { [P in keyof T]?: T[P]; }; /** * From T pick a set of properties K */ type Pick<T, K extends keyof T> = { [P in K]: T[P]; }; /** * Construct a type with a set of properties K of type T */ type Record<K extends string, T> = { [P in K]: T; };
の3つがTSの本体に入ってる。
あとは
/** * Make all properties in T nullable */ type Nullable<T> = { [P in keyof T]: T[P] | null; }; /** * Turn all properties of T into strings */ type Stringify<T> = { [P in keyof T]: string; };
みたいなのが便利なので必要なら自分で定義してねって提案されています。
実際使うの?
react の setState
とか lodash の型定義が賢くなると言われてますが、実際アップデートされたかは見てないです。