Typescript高级: 深入理解extends keyof语法

概述

  • 在TypeScript中,extends关键字是类型系统中一个极其重要的组成部分
  • 它不仅用于类的继承,也是类型兼容性检查和泛型约束的关键机制
  • 特别是当它与keyof关键字结合,形成K extends keyof T的结构时
  • 它为类型系统带来了强大的灵活性和表达能力,让我们能够在泛型中对对象的属性进行操作和约束

K extends keyof T

  • 在TypeScript中,当你声明一个泛型约束为K extends keyof T
  • 这意味着泛型参数K被限制为只能是T类型上存在的属性名的子集
  • 这在处理对象属性、映射类型或者条件类型时非常有用

示例1

ts 复制代码
interface User {
  id: number;
  name: string;
  email: string;
}

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

const user = { id: 1, name: "Alice", email: "alice@example.com"};
console.log(getProperty(user, "name")); // 输出 "Alice"
  • 在这个例子中,K extends keyof User 确保了getProperty函数的key参数, 只能是User接口中定义的属性名

示例2 属性全面转化成只读

ts 复制代码
interface User {
    id: number;
    name: string;
    email: string;
}

type ReadonlyStringFields<T> = {
    readonly [P in keyof T]: T[P];
  }

type ReadonlyUser = ReadonlyStringFields<User>;

// ReadonlyUser 类型为:
// {
//   id: number;
//   readonly name: string;
//   readonly email: string;
// }
  • 这里,ReadonlyStringFields 将所有属性转化为只读,当然

示例3:部分属性只读

ts 复制代码
type MakeSomePropertiesReadonly<T, K extends keyof T> = {
    readonly [P in K]: T[P];
} & {
    [P in Exclude<keyof T, K>]: T[P];
};

interface UserInfo {
    id: number;
    username: string;
    email: string;
    isAdmin: boolean;
}

type ReadonlyUserDetails = MakeSomePropertiesReadonly<UserInfo, 'id' | 'email'>;

function displayUserInfo(user: ReadonlyUserDetails) {
    console.log(`ID: ${user.id}, Email: ${user.email}`);
    // 下面这行如果尝试在真实代码中执行,会因为类型检查而在编译时失败
    // user.id = 123; // Error: Cannot assign to 'id' because it is a read-only property.
    user.username = "NewUsername"; // 这是允许的,因为 username 不是只读的
}

// 假设我们有一个UserInfo实例,为了演示,直接构造一个符合 ReadonlyUserDetails 的对象
const userDetails: ReadonlyUserDetails = {
    id: 42,
    username: "JohnDoe",
    email: "john.doe@example.com",
    isAdmin: false,
};

displayUserInfo(userDetails);
  • 在 MakeSomePropertiesReadonly<T, K>
  • 泛型参数:
    • T: 表示你想要修改属性可读性的原始对象类型
    • K extends keyof T: 表示一个泛型约束,要求 K 必须是 T 类型的键(即属性名)的一个子集。这意味着你可以指定 T 中任意数量和名称的属性来变为只读
  • 类型别名结构:
    • { readonly [P in K]: T[P]; }: 这部分创建了一个新类型,其中 K 集合内的每个属性 P 被声明为只读。[P in K] 是一个映射类型,遍历 K 中的所有键,并为每个键创建一个属性,其值类型与 T[P] 相同,但加上了 readonly 修饰符。
    • & { [P in Exclude<keyof T, K>]: T[P]; } 这部分用来保留 T 类型中未被指定为只读的那些属性。Exclude<keyof T, K> 是一个实用类型,用于从 T 的所有键中排除已经在 K 中的键,确保剩余的属性不被重复定义且保持原样。

K extends keyof any

  • 当K extends keyof any时,这个约束实际上没有起到任何限制作用
  • 因为any类型在TypeScript中是最宽泛的类型,表示可以代表任何类型,所以任何类型都可以被认为是any的键
  • 这通常在你想要泛型参数可以是任何类型时使用,但这种用法在实践中较少见,因为失去了类型安全性的优势

示例

ts 复制代码
function getProperty<K extends keyof any>(obj: any, key: K): any {  
    return obj[key];  
}  
  
const person = {  
    name: 'Alice',  
    age: 30,  
    address: '123 Main Street'  
};  
  
// 由于使用了 keyof any,我们可以传入任何类型的键  
const name = getProperty(person, 'name'); // string  
const age = getProperty(person, 'age'); // number  
const address = getProperty(person, 'address'); // string  
const unknownProp = getProperty(person, 'unknownProp'); // undefined,但不会引发类型错误  
  
console.log(name); // 输出: Alice  
console.log(age); // 输出: 30  
console.log(address); // 输出: 123 Main Street  
console.log(unknownProp); // 输出: undefined
  • keyof any表示任何可能的属性名,因为any类型可以包含任意属性
  • 使用K extends keyof any实际上对K没有太多限制,它可以是任意字符串或符号
  • 这种约束通常不是很有用,因为它不提供关于K具体可能是什么的明确信息

K extends keyof (string | number | symbol)

  • 这个表达式意味着泛型参数K可以是string或symbol类型中任何一个的键名
  • 在TypeScript中,对象的键通常是字符串或符号类型,但不包括数字(除非使用了计算属性名)
  • 因此,这个约束在直觉上可能用于处理特殊情况,比如当你知道泛型参数可能被用于索引一个映射到字符串或符号属性上,但实际上在标准对象操作中,数字作为键的用法不常见

示例

ts 复制代码
type AcceptableKeys = string | number | symbol;  
  
function processKey<K extends AcceptableKeys>(key: K): void {
    console.log(`Processing key: ${key.toString()}`);  
}  

// 使用字符串作为键  
processKey("myStringKey");  
// 使用数字作为键  
processKey(123);  

// 使用符号作为键  
const mySymbol = Symbol("mySymbol");  
processKey(mySymbol);
  • 在这个示例中,我们定义了一个类型别名 AcceptableKeys,它表示可以接受的键类型是字符串、数字或符号
  • 然后,我们定义了一个泛型函数 processKey,它接受一个类型为 K 的参数,其中 K 被约束为必须扩展(extends)AcceptableKeys
  • 这样,我们就可以向 processKey 函数传递字符串、数字或符号类型的参数
相关推荐
看到请催我学习3 小时前
如何实现两个标签页之间的通信
javascript·css·typescript·node.js·html5
天涯学馆10 小时前
Deno与Secure TypeScript:安全的后端开发
前端·typescript·deno
applebomb13 小时前
【2024】uniapp 接入声网音频RTC【H5+Android】Unibest模板下Vue3+Typescript
typescript·uniapp·rtc·声网·unibest·agora
读心悦1 天前
TS 中类型的继承
typescript
读心悦1 天前
在 TS 的 class 中,如何防止外部实例化
typescript
Small-K1 天前
前端框架中@路径别名原理和配置
前端·webpack·typescript·前端框架·vite
宏辉1 天前
【TypeScript】异步编程
前端·javascript·typescript
LJ小番茄2 天前
TS(type,属性修饰符,抽象类,interface)一次性全部总结
前端·javascript·vue.js·typescript
It'sMyGo3 天前
Javascript数组研究03_手写实现_fill_filter_find_findIndex_findLast_findLastIndex
前端·javascript·typescript
bobostudio19953 天前
TypeScript 算法手册【快速排序】
前端·javascript·算法·typescript