如何实现TypeScript泛型函数参数的严格键类型校验?
如何让TypeScript正确校验Hook函数的参数类型
我编写了一个Hook函数,传入对象的键会用于推导返回类型:
type MyOptions = { [key: `${number}${'s'|'ms'}`]: string } function useMyHook<O extends MyOptions>(options: O): MyReturnType<O> { // ... }
调用该函数时,TypeScript不会对无效键报错:
useMyHook({ foo: 'bar' })
但如果直接定义变量并指定类型,TypeScript会正常识别无效键:
const options: MyOptions = { foo: 'bar' }
请问怎么才能让函数参数也被正确校验?
问题根源
用O extends MyOptions做泛型约束时,TypeScript会基于结构类型兼容性判断:只要传入对象的结构和MyOptions兼容(哪怕包含额外键),就会被允许。因为MyOptions的索引签名只定义了允许的键格式,并没有禁止额外键,所以传入foo这类无效键时,TypeScript会把O推断为{ foo: string } & MyOptions,不会触发报错。
而直接给变量指定MyOptions类型时,TypeScript会执行严格赋值检查,不允许存在索引签名未覆盖的额外键,因此会正常报错。
解决方案
要让函数参数严格符合MyOptions的键格式,需要修改泛型约束,强制O的所有键都匹配${number}${'s'|'ms'}规则,以下是两种可行方案:
方案1:双重约束确保键格式合法
通过MyOptions & Record<keyof O, ${number}${'s'|'ms'}>强制所有键都符合要求:
type MyOptions = { [key: `${number}${'s'|'ms'}`]: string } type MyReturnType<O> = { [K in keyof O]: number } function useMyHook<O extends MyOptions & Record<keyof O, `${number}${'s'|'ms'}`>>(options: O): MyReturnType<O> { // 示例实现 return Object.fromEntries( Object.entries(options).map(([k, v]) => [k, parseInt(v)]) ) as MyReturnType<O> } // 报错:类型“"foo"”的属性不能赋给类型“`${number}${"s" | "ms"}`”的索引类型 useMyHook({ foo: 'bar' }) // 正常通过校验 useMyHook({ '100s': '100', '200ms': '200' })
方案2:反向键约束(TypeScript 4.9+)
利用keyof MyOptions会被解析为${number}${'s'|'ms'}的特性,直接约束键的类型:
type MyOptions = { [key: `${number}${'s'|'ms'}`]: string } type MyReturnType<O> = { [K in keyof O]: number } function useMyHook<O extends Record<keyof O, string> & { [K in keyof O]: K extends keyof MyOptions ? string : never }>(options: O): MyReturnType<O> { // 示例实现 return Object.fromEntries( Object.entries(options).map(([k, v]) => [k, parseInt(v)]) ) as MyReturnType<O> } // 同样会触发无效键报错 useMyHook({ foo: 'bar' })
方案3:直接使用MyOptions作为参数类型(无键类型推导)
如果不需要返回类型精确对应传入的具体键,直接把参数类型设为MyOptions即可,但这样返回类型无法推导具体键名:
function useMyHook(options: MyOptions): MyReturnType<MyOptions> { // ... }
如果需要保留返回类型和传入键的一一对应关系,方案1是更优选择。
内容的提问来源于stack exchange,提问作者Spadar Shut
相关产品推荐
相关产品推荐

