如何在TypeScript中限制类允许属性并解决keyof索引签名问题
TypeScript类属性名称与类型限制方案
针对你遇到的父类索引签名导致keyof T包含所有字符串、无法提取子类显式属性的问题,我们可以通过移除索引签名+递归类型约束+编译/运行时双重检查的方案解决,具体步骤如下:
1. 核心思路
放弃父类的字符串索引签名(这是导致keyof范围过大的根源),改用递归类型工具约束类及其嵌套对象的属性,同时结合装饰器实现运行时校验,确保子类既不能直接使用禁止键,嵌套对象也会被递归检查。
2. 类型工具定义
首先定义禁止键类型和递归约束工具:
// 定义禁止使用的属性名称 type ForbiddenKeys = 'x' | 'y' | 'z'; // 递归约束:对象及其嵌套对象不能包含禁止键 type NoForbiddenKeys<T> = T extends object ? { [K in keyof T as K extends ForbiddenKeys ? never : K]: NoForbiddenKeys<T[K]> } : T; // 编译时断言工具:用于检查类是否符合约束(不符合则编译报错) type AssertValid<T extends NoForbiddenKeys<T>> = T;
3. 调整父类与子类定义
移除父类的索引签名,改用类型约束确保子类合规:
// 抽象父类:移除索引签名,保留原有装饰器逻辑 abstract class A { // 示例:父类原有方法 baseMethod() { /* ... */ } } // 合规子类:显式声明属性,嵌套对象也符合约束 class ValidSub extends A { name: string; nestedInfo: { id: number; meta: { desc: string } }; constructor(name: string, nestedInfo: { id: number; meta: { desc: string } }) { super(); this.name = name; this.nestedInfo = nestedInfo; } } // 编译时校验:无报错说明合规 type CheckValidSub = AssertValid<ValidSub>; // 错误示例:包含禁止键的子类(编译阶段直接报错) class InvalidSub extends A { x: number; // 编译报错:类型“number”不能赋值给类型“never” nested: { y: string }; // 编译报错:嵌套对象包含禁止键 }
4. 提取子类显式属性
由于父类无索引签名,keyof ValidSub仅包含子类显式声明的属性+父类方法,通过Omit可以精准提取子类自己的属性:
// 提取子类的显式属性(排除父类的方法/属性) type SubExplicitProps = Omit<keyof ValidSub, keyof A>; // 结果:"name" | "nestedInfo"
5. 装饰器添加运行时校验
结合装饰器实现运行时的属性检查,确保即使绕过编译检查(如动态添加属性)也会被拦截:
function enforceNoForbiddenKeys<T extends typeof A>(target: T) { return class extends target { constructor(...args: any[]) { super(...args); // 递归检查对象属性 const validateObj = (obj: any) => { for (const key in obj) { if (['x', 'y', 'z'].includes(key)) { throw new Error(`禁止使用属性:${key}`); } // 递归检查嵌套对象 if (typeof obj[key] === 'object' && obj[key] !== null) { validateObj(obj[key]); } } }; validateObj(this); } }; } // 给子类添加装饰器 @enforceNoForbiddenKeys class ValidSub extends A { // ... 原有定义 } // 运行时校验:动态添加禁止键会报错 const sub = new ValidSub('test', { id: 1, meta: { desc: 'demo' } }); sub.x = 123; // 运行时抛出错误:禁止使用属性:x
6. 嵌套对象约束说明
NoForbiddenKeys工具类型会递归处理所有嵌套对象,比如如果子类的nestedInfo包含x属性,编译阶段直接报错;如果运行时动态添加,装饰器也会拦截。
内容的提问来源于stack exchange,提问作者asnaeb
相关产品推荐
相关产品推荐

