TYPESCRIPT / FOUNDATIONS

泛型与类型关联

让可复用逻辑保留输入之间及输入与输出之间的类型关系。

本章目录CONTENTS ↓

一个通用属性读取函数既要限制合法键,又要保留“选中哪个键,就返回哪个属性类型”的关系。泛型通过类型参数记录这种关联,使同一段逻辑能够复用于不同输入。

为什么需要泛型记录对象与键的关系

通用属性读取函数需要同时做到两件事:键必须存在,返回类型还要随本次选中的键变化。只把返回类型写成全部属性值的联合,会让读取一个明确字符串字段的调用也得到数字、布尔值等无关可能性。

泛型参数用于记录输入之间、输入与输出之间的关系:T 对应对象,K 对应这次选中的键,再用 T[K] 表达结果。这样的函数适合不同对象共用读取逻辑;若只是固定对象的一次访问,直接写对象属性即可,不需要额外包装一个泛型函数。

让选中的键决定返回类型

ts
function readProperty<T, K extends keyof T>(
  obj: T, key: K
): T[K] {
  return obj[key];
}

const settings = { theme: "dark", retries: 3, offline: false };
const theme = readProperty(settings, "theme"); // string

T 关联对象类型,K 关联本次键实参的类型,T[K] 取对应属性的值类型。这里普通对象的 theme 属性推导为 string

把这次调用拆成四步:

  1. 实参静态类型settings 包含 theme: stringretries: numberoffline: boolean
  2. 推导T 采用该对象类型,K 在本次调用中为字面量类型 "theme"
  3. 约束"theme" 满足 keyof T 的合法键范围。
  4. 求值T["theme"] 得到 string

K extends keyof T 限制 K 的合法范围,并不要求 K 等于全部键。上述步骤描述本例的理解路径,不是编译器所有推断场景的完整算法。

键实参是联合时

ts
const key: "theme" | "offline" =
  Math.random() > 0.5 ? "theme" : "offline";
const value = readProperty(settings, key);
// K = "theme" | "offline"
// T[K] = T["theme"] | T["offline"]
//      = string | boolean

编译期只知道 key 可能是这两个键,因此返回值也是对应两种类型的联合。它不会等运行时选中键后再决定泛型类型。

为什么只有 keyof T 还不够

ts
function readLoose<T>(obj: T, key: keyof T): T[keyof T] {
  return obj[key];
}

const loose = readLoose(settings, "theme");
// string | number | boolean

这个返回类型始终包含全部属性值,没有独立的 K 记录本次键类型。

官方阅读

这一页,先记到这里。