<T, K extends keyof T> をちゃんと理解する
TL;DR
TypeScriptでライブラリなどを覗いているとよく見かける<T, K extends keyof T>
について、問題を分解してきちんと理解する試みです。
extends
この場合のextends
の役割は継承ではなく、制約です。
例えば以下のようなイメージ。
type StringOrNumber<T extends string | number> = T
type A = StringOrNumber<string>
type B = StringOrNumber<number>
// Type 'boolean' does not satisfy the constraint 'string | number'
type C = StringOrNumber<boolean>
エラーメッセージの指摘がまさにそうですよね。T
はthe constraint 'string | number'
です。
次はオブジェクト型の場合を見てみます。
type User = {
id: number;
name: string;
};
const getUserName = <T extends User>(arg: T): string => arg.name;
getUserName({ id: 123, name: 'aaa' });
getUserName({ id: 123, name: 'aaa', email: 'aaaaaa' });
// Argument of type '{ id: number; }' is not assignable to parameter of type 'User'.
// Property 'name' is missing in type '{ id: number; }' but required in type 'User'
getUserName({ id: 123 });
こちらも同じくtype 'User'では'name'が必須です
という旨のことを言われています。
つまり、K extends T
とは少なくとも、KはTのプロパティ・型を持ってないとダメということになります。
keyof
こちらは比較的簡単な話で、keyof
はオブジェクトの型からプロパティ名を型として返す型演算子です。
type User = {
id: number;
name: string;
};
// このコードはどちらも同じ意味
type UserKey = keyof User; // "id" | "name"
const id: UserKey = "id"
const useName: UserKey = "name"
// Type '"age"' is not assignable to type 'keyof User'.
const age: UserKey = "age"
<T, K extends keyof T> を理解する
例えばT
が先ほどのUser
だったとします。そのとき、keyof User
は"id" | "name"
となります。
かつ、extends
をふまえるとK extends "id" | "name"
ということになります。
つまりK
は"id"
,"name"
, "id" | "name"
のいずれかであれば取ることができますが、"age"
や"id" | "age"
をとることはできません。
で、なにが便利なの?
それを知るにはtype-challengesのこの問題を解いてみましょう。
この問題を解くためには、extends
, keyof
, Mapped Types
の3つの知識を組み合わせる必要があります。
組み込みの型ユーティリティPick<T, K>を使用せず、TからKのプロパティを抽出する型を実装します。
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
ここで、MyPick
をこのように定義することができます。
type MyPick<T, K extends keyof T> = {
[key in K]: T[key]
}
K
はT
のいずれかのプロパティとなり、そのプロパティをkey
として取り出します。
その上で、T[key]
よりプロパティkey
に対応する型をセットします。
結果として、'title' | 'completed'
のようにPick
したいプロパティを指定すれば、Pick
と同等の結果を得られるというわけです。
おわりに
type-challengesの初級のしょっぱなの問題がこれだったのですが、正直「むずすぎん…?」て感じでした。
いくつかの基本を組み合わせて解かないとダメなので、これ完全に応用じゃん…てなりました。
ただしっかり言語化していくと、とても良い勉強にはなったので良問でした👍
📢 Kobe.tsというTypeScriptコミュニティを主催しています
フロント・バックエンドに限らず、周辺知識も含めてTypeScriptの勉強会を主催しています。
毎朝オフラインでもくもくしたり、神戸を中心に関西でLTもしています。
盛り上がってる感を出していきたいので、良ければメンバーにだけでもなってください😣
Discussion