TypeScript如何在不干扰键校验的前提下强制类参数类型
问题根因
该冲突的本质是:类上显式声明字符串索引签名后,keyof 类实例 类型会被直接拓宽为 string,自然丢失了固定字面量key的联合类型校验能力,因此不能通过加索引签名的方式约束成员类型。
解决方案
可以通过「整体结构校验」的方式,既保证所有Operation成员符合接口定义,又不丢失固定key的类型信息,分两种实现方式:
方案1:TS 4.9+ 用 satisfies 操作符(推荐)
satisfies 可以在不修改原有类型的前提下,校验值是否符合目标类型,刚好适配需求:
// Operation 基础接口定义 interface Operation { url: string parameters: Record<string,string> } // 约束所有属性都是 Operation 的工具类型 type OperationSchema = Record<string, Operation> // 定义 Operation 配置类,末尾用 satisfies 做整体校验 class BaseOperations { one = { url: '/one', parameters: {p: '1'} } two = { // 此处如果写 uarl 这类不符合 Operation 的属性,会直接在定义时报错 url: '/two', parameters: {} } } satisfies OperationSchema // 通用调用器代码无需大幅修改,只需要把泛型约束改为 OperationSchema function runOperation<T extends OperationSchema>(type: {new(): T;}, keyword: keyof T) { const operation = new type(); // cache_and_run(operation[keyword]); } // 调用校验完全正常 function main() { runOperation(BaseOperations, 'one'); // 合法 runOperation(BaseOperations, 'two'); // 合法 runOperation(BaseOperations, 'three'); // VS Code 会直接抛出不存在该key的错误 }
方案2:低版本TS 用辅助校验函数
如果TS版本低于4.9,不支持satisfies,可以用一个泛型辅助函数实现同样的效果:
interface Operation { url: string parameters: Record<string,string> } type OperationSchema = Record<string, Operation> // 辅助校验函数,接收类构造器,校验通过后原封不动返回 function defineOperations<T extends OperationSchema>(cls: new () => T) { return cls } // 定义配置类时用辅助函数包裹即可自动校验 const BaseOperations = defineOperations(class { one = { url: '/one', parameters: {p: '1'} } two = { url: '/two', parameters: {} } }) // 调用逻辑和方案1完全一致 function runOperation<T extends OperationSchema>(type: {new(): T;}, keyword: keyof T) { const operation = new type(); // cache_and_run(operation[keyword]); } // 调用校验完全正常 function main() { runOperation(BaseOperations, 'one'); // 合法 runOperation(BaseOperations, 'two'); // 合法 runOperation(BaseOperations, 'three'); // VS Code 会直接抛出不存在该key的错误 }
方案2:低版本TS 用辅助校验函数
如果TS版本低于4.9,不支持satisfies,可以用一个泛型辅助函数实现同样的效果:
interface Operation { url: string parameters: Record<string,string> } type OperationSchema = Record<string, Operation> // 辅助校验函数,接收类构造器,校验通过后原封不动返回 function defineOperations<T extends OperationSchema>(cls: new () => T) { return cls } // 定义配置类时用辅助函数包裹即可自动校验 const BaseOperations = defineOperations(class { one = { url: '/one', parameters: {p: '1'} } two = { url: '/two', parameters: {} } }) // 调用逻辑和方案1完全一致 function runOperation<T extends OperationSchema>(type: {new(): T;}, keyword: keyof T) { const operation = new type(); // cache_and_run(operation[keyword]); }
扩展支持
如果需要做多层继承扩展配置,只需要给子类也加上同样的校验即可:
class ExtendedOperations extends BaseOperations { three = { url: '/three', parameters: {id: ''} } } satisfies OperationSchema runOperation(ExtendedOperations, 'three') // 合法 runOperation(ExtendedOperations, 'four') // 报错
内容的提问来源于stack exchange,提问作者Hugo Aboud
相关产品推荐
相关产品推荐

