TypeScript如何为索引签名的getter和setter分别定义不同类型
实现方案
前提要求
确保你使用的 TypeScript 版本 ≥ 4.3,该版本开始官方支持 getter 和 setter 定义不同类型,满足你读写分离的类型约束需求。
调整后的泛型类型
你可以直接替换原有的EditableObject定义,不需要修改配套的editableScope工具函数:
// 判断是否为普通对象,排除数组、日期、正则等特殊类型,可根据业务需求调整规则 type IsPlainObject<T> = T extends object ? T extends Array<any> | Function | Date | RegExp | Map<any, any> | Set<any> ? false : true : false; export type EditableObject<T> = { // 读取属性时匹配该分支,返回非空类型 [K in keyof T]: IsPlainObject<T[K]> extends true ? EditableObject<T[K]> : T[K] // 写入属性时匹配该分支,允许传入undefined/null } & { [K in keyof T]?: IsPlainObject<T[K]> extends true ? EditableObject<T[K]> | undefined | null : T[K] | undefined | null }
验证效果
你可以用以下代码测试类型约束是否符合预期:
interface User { name: string; age: number; ext: { desc: string; } } const demoUser: User = { name: "测试", age: 20, ext: { desc: "测试描述" } }; editableScope(demoUser, (editUser) => { // 写入允许空值,不会报错 editUser.name = undefined; editUser.age = null; editUser.ext.desc = undefined; // 读取自动推导为非空类型,符合strictNullChecks要求 const name = editUser.name; // 类型为string,无空类型联合 const age = editUser.age; // 类型为number const desc = editUser.ext.desc; // 类型为string });
实现原理
- 利用 TypeScript 交叉类型的优先级特性,读取属性时会优先匹配更精确的非空定义分支,写入属性时会匹配更宽松的允许空值的可选分支
- 内置普通对象判断逻辑,避免对数组、日期等特殊类型做递归处理,你也可以根据业务场景调整判断规则
- 完全兼容原有工具函数逻辑,替换类型定义即可直接在现有项目中落地
内容的提问来源于stack exchange,提问作者kemsky
相关产品推荐
相关产品推荐

