如何使用TypeScript泛型更新接口的可写字符串属性?
解决方案
要实现满足你需求的泛型函数,需要同时完成筛选可写字符串属性、禁止只读属性和类型安全赋值三个目标,以下是具体实现:
类型定义与函数实现
首先定义筛选规则,再实现泛型函数:
// 辅助类型:判断两个类型是否完全相等 type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false; // 筛选T中「可写」且「类型为string/可选string」的属性键 type WritableStringKeys<T> = { [P in keyof T as T[P] extends string | undefined ? P : never]: T[P] } extends { [Q in infer K]: infer V } ? K extends keyof T ? // 对比原属性与移除readonly后的属性,一致则说明非只读 Equal<T[P], { -readonly [Q in P]: T[P] }[P]> extends true ? K : never : never : never; // 泛型函数实现 function doSomething<T, K extends WritableStringKeys<T>>(t: T, key: K) { // 类型断言确保赋值兼容string/可选string类型 t[key] = "123" as T[K]; }
测试验证
用你提供的Whatever接口测试:
interface Whatever { a: string; b?: string; readonly c: string; d: number; e?: boolean; }; const w: Whatever = { a: "this", b: "is", c: "frustrating", d: 0 } doSomething(w, "a"); // ✅ 正常运行(可写string属性) doSomething(w, "b"); // ✅ 正常运行(可选string属性) doSomething(w, "c"); // ❌ 编译错误(readonly属性被禁止) doSomething(w, "d"); // ❌ 编译错误(number类型属性被排除) doSomething(w, "e"); // ❌ 编译错误(boolean类型属性被排除) doSomething(w, "f"); // ❌ 编译错误(不存在的属性)
关键逻辑说明
筛选字符串属性:
通过映射类型[P in keyof T as T[P] extends string | undefined ? P : never]只保留类型为string或undefined的属性键。排除只读属性:
利用Equal辅助类型对比原属性和移除readonly修饰符后的属性类型,若一致则说明该属性是可写的(只读属性的类型会与移除readonly后的类型存在差异)。类型安全赋值:
泛型K extends WritableStringKeys<T>让TypeScript准确推断T[K]的类型为string | undefined,使用as T[K]确保字符串赋值与目标类型兼容。
简化版本(TypeScript 4.4+)
如果不需要兼容旧版TypeScript,可使用更简洁的写法:
// 直接筛选可写的string/可选string属性 type WritableStringKeys<T> = keyof { [P in keyof T as T[P] extends string | undefined ? P : never]-?: T[P] }; function doSomething<T, K extends WritableStringKeys<T>>(t: T, key: K) { t[key] = "123" as T[K]; }
内容的提问来源于stack exchange,提问作者Kevin Dean
相关产品推荐
相关产品推荐

