Kodokon kodokon.com

进阶泛型:约束与默认值

你将用 extends 约束类型参数,并提供默认值,从而构建符合人体工学的泛型 API。

10 分钟 · 3 题

在 Kodokon 中打开本课

无约束的泛型是不透明的:在函数体内部,T 什么都不让你做。extends 子句在类型参数上建立起一份最小契约,从而在保留调用方精确类型的同时,解锁对那些有保证的属性的访问。这正是它与用接口标注参数的根本区别:约束保留了信息,而接口抹去了信息。

TYPESCRIPT
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]);
word 仍被类型化为 string,list 为 number[]。

K extends keyof T 这一组合是日常最划算的模式:它把两个类型参数关联起来,并在静态层面保证某个键确实属于该对象。返回类型 T[K](一种索引访问类型)会精确地跟踪所请求的属性。工具库中的 pickpluck 这类函数正是这样被类型化的。

TYPESCRIPT
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");
port 被推断为 number,host 为 string;任何其他键都会被拒绝。

默认值T = string)让泛型在调用处变为可选:只有当用户想偏离常见情况时,才需要提供类型参数。与约束结合(T extends X = Y)时,它同时定义了上界和隐式行为。默认值必须满足约束,否则声明本身就无法编译。这是 HTTP 客户端、状态存储以及类型化事件发射器的标准模式。

TYPESCRIPT
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");
不提供类型参数时,data 会退回到 unknown,而不是 any。

知识检测

确认你已牢记本课的重点内容。

  1. 在泛型签名中,约束 K extends keyof T 保证了什么?
    • K 可以是任意字符串
    • K 是 T 的一个已存在的键,在编译期被检查
    • T 至少包含一个可选键
    • K 和 T 是同一个类型
  2. 相比不使用泛型的 longest(a: { length: number }, b: ...)longest<T extends { length: number }> 有什么优势?
    • 它运行得更快
    • 它在返回值中保留了调用方的精确类型
    • 它接受更多的输入类型
  3. 为什么在默认值上优先选择 <T = unknown> 而非 <T = any>
    • unknown 比 any 编译得更快
    • 严格模式下禁止使用 any
    • unknown 迫使调用方在使用前收窄类型,而 any 会禁用所有检查
    • unknown 接受的值比 any 少