限制泛型类型为已知属性:TypeScript create函数参数校验问题
问题
我有一个create泛型函数,它接收Settings类型的可选参数,返回的FuncFact类型结构会随传入的Settings参数变化。当前实现里,传入未知属性(比如thisShouldMakeTSCompilerAngry)时TypeScript不会报错,但我希望能禁止这类未知属性。
当前代码实现如下:
// 所有属性都是可选的 interface Settings { name?: string; usable?: boolean; values?: { [key: string]: string }; } type FuncFact<S = Settings> = S extends { values?: { [key: string]: string } } ? Record<keyof S['values'], Function> : Record<never, Function> declare function create<MySettings extends Settings>(settings?: MySettings): FuncFact<MySettings>; const z = create({ name: 'John', values: { up: '+1', down: '-1' }, thisShouldMakeTSCompilerAngry: 'dummy' // 这里希望TS编译器报错,只允许Settings的属性 }); z.up(); // 这个功能需要保留正常工作
我知道create必须是泛型函数,不能直接用Settings作为参数类型,因为要根据传入的values结构推导返回类型。我考虑过用keyof MySettings extends keyof Settings来限制,但不确定怎么实现。
解决方案
要实现严格的属性校验,同时保留泛型推导能力,可以通过约束泛型类型与Settings完全匹配来实现。下面提供两种可行的写法:
写法一:直接在泛型约束中限制
// 所有属性都是可选的 interface Settings { name?: string; usable?: boolean; values?: { [key: string]: string }; } type FuncFact<S = Settings> = S extends { values?: infer V } ? V extends { [key: string]: string } ? Record<keyof V, Function> : Record<never, Function> : Record<never, Function> // 泛型约束:确保MySettings的所有键都属于Settings的键 declare function create<MySettings extends Settings & Record<keyof MySettings, keyof Settings>>( settings?: MySettings ): FuncFact<MySettings>; const z = create({ name: 'John', values: { up: '+1', down: '-1' }, thisShouldMakeTSCompilerAngry: 'dummy' // TS编译器会报错,符合预期 }); z.up(); // 依然可以正常调用,类型推导正常
解释
- 泛型约束
MySettings extends Settings & Record<keyof MySettings, keyof Settings>的作用是:既让MySettings继承Settings的属性定义,又强制MySettings的所有键都只能是Settings中已存在的键。一旦传入未知键,TypeScript会检测到keyof MySettings包含不属于keyof Settings的字段,直接触发报错。 - 优化后的
FuncFact用infer V推导values的具体类型,让类型逻辑更清晰,同时保留原有功能——根据传入的values结构生成对应的函数记录类型。
写法二:用辅助类型实现Exact匹配
// 所有属性都是可选的 interface Settings { name?: string; usable?: boolean; values?: { [key: string]: string }; } type FuncFact<S = Settings> = S extends { values?: infer V } ? V extends { [key: string]: string } ? Record<keyof V, Function> : Record<never, Function> : Record<never, Function> // 定义Exact辅助类型,强制两个类型完全匹配 type Exact<T, U> = T extends U ? (U extends T ? T : never) : never; declare function create<MySettings extends Settings>( settings?: Exact<MySettings, Settings> ): FuncFact<MySettings>; const z = create({ name: 'John', values: { up: '+1', down: '-1' }, thisShouldMakeTSCompilerAngry: 'dummy' // TS编译器会报错,符合预期 }); z.up(); // 功能正常保留
解释
Exact<T, U>类型的作用是仅当T和U的结构完全一致时才返回T,否则返回never。把它作为参数类型约束后,传入未知属性时,Exact<MySettings, Settings>会变成never,触发编译器报错,同时不影响正常的泛型推导逻辑。
内容的提问来源于stack exchange,提问作者UndefinedBehavior
相关产品推荐
相关产品推荐

