如何为Person类型实现类型安全的DisabledKeys映射对象类型扩展?
解决TypeScript嵌套对象的类型安全禁用键集合问题
这个问题确实很常见——当需要为嵌套对象创建对应的禁用键集合时,既要保证类型安全,又要避免冗余和不同步的问题。咱们一步步来解决它:
首先先明确基础类型(把Passport抽出来,方便后续维护和复用):
type Passport = { series: string; number: string }; type Person = { email: string; name: string; passport: Passport; };
为什么原来的实现有问题?
你最初的交叉类型写法存在两个核心缺陷:
- 允许额外属性:交叉类型
{ [key in keyof Person]?: boolean } & { ... }会放松类型限制,导致像foo: number这种不属于Person的键也能通过编译 - 缺乏自动同步:当
Person中的passport属性重命名时,DisabledKeys里的passport键不会触发编译错误,导致类型定义和原类型不同步
正确的类型安全实现方案
我们可以创建一个递归映射类型,它会遍历原类型的每个属性,同时支持嵌套对象的禁用键定义,并且严格限制只能使用原类型的键:
// 递归生成嵌套的禁用键类型 type NestedDisabled<T> = { [K in keyof T]?: boolean | (T[K] extends object ? NestedDisabled<T[K]> : never); }; // 基于Person生成严格的禁用键类型 type DisabledKeys = NestedDisabled<Person>;
这个类型的核心特性
- 严格限制键范围:只能使用
Person中存在的键(email、name、passport),添加任何额外键都会直接触发编译错误 - 自动同步属性名:如果
Person中的passport重命名为idCard,DisabledKeys里的对应键也会自动更新,旧的passport键会立即报错,保证类型同步 - 支持两种禁用方式:
- 对于普通属性(比如
email、name),可以直接设为boolean表示是否禁用 - 对于嵌套对象属性(比如
passport),既可以设为boolean(禁用整个嵌套对象),也可以传入嵌套的禁用键对象(比如{ series: true }表示只禁用passport.series)
- 对于普通属性(比如
合法使用示例
// 合法:禁用email,以及passport的series字段 const validDisabledKeys: DisabledKeys = { email: true, passport: { series: true } }; // 合法:禁用整个passport对象 const validDisabledKeys2: DisabledKeys = { passport: true };
非法示例(会触发编译错误)
// 错误:'foo'不属于Person的键 const invalidKeys1: DisabledKeys = { foo: 123 }; // 错误:如果Person的passport已重命名为idCard,这里的passport键不存在 const invalidKeys2: DisabledKeys = { passport: { series: true } };
可选:严格模式(禁止直接把嵌套对象设为boolean)
如果你希望嵌套对象必须明确指定内部的禁用键,不能直接设为boolean,可以调整递归类型:
type NestedDisabledStrict<T> = { [K in keyof T]?: T[K] extends object ? NestedDisabledStrict<T[K]> : boolean; }; type DisabledKeysStrict = NestedDisabledStrict<Person>;
这样passport只能传入嵌套对象,不能直接写passport: true。
内容的提问来源于stack exchange,提问作者Maksim Nesterenko
相关产品推荐
相关产品推荐

