TypeScript 的 keyof

224 阅读2分钟

基础概念

keyof操作符允许我们从一个对象类型中提取所有键名(属性名)组成一个联合类型。这一功能对于编写类型安全的访问器函数、映射类型以及泛型约束特别有用。

interface Person {
  name: string;
  age: number;
}

通过keyof操作符,我们可以获取Person接口的所有键名组成的类型

type PersonKey = keyof Person; // 结果为:'name' | 'age' 

PersonKey类型表示的是nameage的联合类型,这为后续的类型操作提供了基础。

与映射类型结合使用

keyof真正厉害在于它与映射类型的合作。映射类型允许我们基于一个现有的类型创建新的类型,而keyof提供了映射的“索引”。例如,我们可以通过映射类型来创建一个具有可选属性的相同结构类型:

type PersonWithOptionalKeys = {
  [K in keyof Person]?: Person[key];
}
// 结果为:{ name?: string; age?: number; }

这样,PersonWithOptionalKeys类型就允许nameage属性为可选。

泛型中的应用

在泛型上下文中,keyof可以作为类型参数约束,使泛型变得更加灵活且类型安全。考虑一个通用的属性获取函数:

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

const person: Person = { name: 'PS', age: 18 }
console.log(getProperty(person, 'name')); // "PS"
// 下面这行会报错,因为 'sex' 不是 Person 的一个属性
// console.log(getPersonField(person, 'sex'));

这个函数接受任何对象T和其键名K,并安全地返回对应的属性值。类型系统会确保key参数只能是T类型的实际键名之一。

提取特定类型的属性

有时候我们需要从一个类型中提取特定类型的属性。例如,我们想要从一个借口中提取所有方法。

interface Person {
  time: number;
  eat: (msg: string) => Primise(string);
  wear: (msg: string) => number;
}

type Methods<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];

type PersonMethods = Methods<Person>; // "eat" | "wear"

类型安全的switch语句

在处理动态键值时,确保switch语句的类型安全至关重要,通过keyof和类型断言,我们可以实现这一点:

function handleProperty(person: Person, key: keyof Person){
  switch(key){
    case 'name':
      console.log(person.name.toUpperCase());
      break;
    case 'age':
      console.log(`Age: ${person.age}`);
      break;
    // 无需 default 分支,因为 TypeScript 确保 key 只能是 'name' 或 'age'
  }
}

这段代码中,由于key的类型限制,编译器会强制我们处理所有可能的键值,从而避免了逻辑上的遗漏。

我是菜逼,大佬勿喷。