extends で型パラメータを制約し、デフォルトを提供して、扱いやすいジェネリック API を構築します。
このレッスンを Kodokon で開く制約のないジェネリックは不透明です。関数本体の中では、T に対して何もできません。extends 句は型パラメータに最小限の契約を課し、これによって保証されたプロパティへのアクセスが可能になりつつ、呼び出し側の正確な型が保持されます。これがインターフェースで型付けされた引数との根本的な違いです。制約は情報を保持し、インターフェースはそれを消し去ります。
function longest<T extends { length: number }>(
a: T,
b: T
): T {
return a.length >= b.length ? a : b;
}
const word = longest("hello", "worldwide");
const list = longest([1, 2], [3, 4, 5]);K extends keyof T の組み合わせは、日常で最も役立つパターンです。2 つの型パラメータを互いに結び付け、あるキーが実際にオブジェクトに属することを静的に保証します。返り値の型 T[K](インデックスアクセス型)は、要求されたプロパティを正確に追跡します。これはユーティリティライブラリの pick や pluck のような関数がまさにこうして型付けされている方法です。
function getProp<T extends object, K extends keyof T>(
obj: T,
key: K
): T[K] {
return obj[key];
}
const config = { port: 8080, host: "localhost" };
const port = getProp(config, "port");
const host = getProp(config, "host");デフォルト(T = string)は、呼び出し側でジェネリックを省略可能にします。利用者は、よくあるケースから外れたいときにだけ型引数を渡せばよくなります。制約と組み合わせると(T extends X = Y)、上限と暗黙の振る舞いの両方を定義できます。デフォルトは制約を満たさなければならず、そうでないと宣言自体がコンパイルできません。これは HTTP クライアント、ストア、型付きイベントエミッターの標準的なパターンです。
interface ApiResponse<T = unknown> {
status: number;
data: T;
}
async function fetchJson<T = unknown>(
url: string
): Promise<ApiResponse<T>> {
const res = await fetch(url);
return { status: res.status, data: await res.json() };
}
interface Post { id: number; title: string }
const typed = await fetchJson<Post>("/api/posts/1");
const loose = await fetchJson("/api/health");K extends keyof T という制約は何を保証しますか?longest(a: { length: number }, b: ...) に対して、longest<T extends { length: number }> にはどんな利点がありますか?<T = any> よりも <T = unknown> を優先するのはなぜですか?