高级 Typescript 泛型教程

问题

假设我们有一个名为 Attributes 的类,它管理不同类型的属性——即 EmployeeAttributesClientAttributes 等。

type EmployeeProps = {
  name: string;
  salary: number;
};

export class Attributes<T> {
  constructor(private data: T) {}

  get(key: string): string | number {
    return this.data[key];
  }
}

const attrs = new Attributes<EmployeeProps>({
  name: 'Tom',
  salary: 5000,
});

// name 是 string 或 number 类型,因为这是我们硬编码为 get 方法的返回
const name = attrs.get('name');

在上面的例子中,我们创建了一个 Attributes 类的实例,它传入了一个 EmployeeProps 类型的泛型,它具有 name 和 Salary 键,分别是 string 和 number 类型。

然后我们在实例上使用 get 方法并获取 name 属性,但是我们在返回值上获取的类型是字符串或数字,这意味着我们只能在使用类型之前对其进行字符串和数字通用的操作守卫以缩小值的类型。

我们应该能够告诉 typescript name 变量应该是字符串类型,所以在这种情况下我们不必使用类型保护。

我们正在尝试做的是将 get 方法采用的参数限制为姓名或薪水。姓名或薪水是类型 T 中唯一有效的键,这意味着我们应该只根据这些键从数据对象中获取值。

一旦我们将 get 方法的参数约束为类型 T 的键之一,我们应该将 get 方法的返回值指定为与传入的键对应的类型。

因此,如果我们例如调用:

get('name');

T 类型中 name 键对应的类型是 string,这就是 get 方法应该返回的类型。

解决方案 – K extends keyof T 泛型

我们可以在 get 方法上使用泛型,我们指定泛型类型 K 只能是在初始化时传递给类的泛型类型 T 的键之一。

如果 get 方法的参数仅被约束为 T 接口的键之一,即数据类属性的签名,则 get 方法应返回 T[K] – 从类型中键 K 的类型 T

type EmployeeProps = {
  name: string;
  salary: number;
};

export class Attributes<T> {
  constructor(private data: T) {}

  get<K extends keyof T>(key: K): T[K] {
    return this.data[key];
  }
}

const attrs = new Attributes<EmployeeProps>({
  name: 'Tom',
  salary: 5000,
});

// is now of type string
const name = attrs.get('name');

// ERROR: Argument of type '"test"' is not assignable to parameter of type '"name" | "salary"'.
const test = attrs.get('test');

高级 Typescript 泛型教程

如果我们现在将鼠标悬停在 name 变量上,我们应该会看到它是字符串类型,因为我们已经指定 get 方法返回 T[K],这是一个对象查找,就像在 js 中一样 – 它查找键的值 对象 T 中的 K – 在我们的例子中是 EmployeeProps 类型的键名的值,它是字符串。

上述代码段中的要点:

  • K 的类型只能是 T 的键之一——在我们的例子中是 name 或 salary
  • 换句话说 – 我们只能用 T 的键之一调用 get – name 或 salary
  • 对于 get 的返回值,我们说查看键 K 处的类型 T 并返回值。 在我们的例子中,查看键名处的 EmployeeProps 类型并返回字符串

如何处理

我们只能这样做,因为在 typescript 中,字符串可以是类型。 例如,字符串“world”可以是“world”类型。

type World = 'world';

function logHello(message: World) {
  console.log(`Hello ${message}`);
}

// ERROR: Argument of type '"all"' is not assignable to parameter of type '"world"'.
logHello('all');

logHello('world');

高级 Typescript 泛型教程

EmployeeProps 接口的键是 name 和 salary 类型的字符串,这允许我们将 get 方法接收的参数限制为特定键。 一旦我们这样做了 – 我们所要做的就是将 get 方法的返回值注释为 EmployeeProps 类型中特定键 K 的查找。