TypeScript中编辑Record时如何正确推断Generic Key类型?
问题:配置泛型实现不可变布尔记录更新函数
我需要实现一个不可变更新布尔记录的函数,接收布尔值类型的Record和一组匹配键,返回新Record并将指定键的值设为true。但当前泛型配置导致编译错误——TypeScript仅从keys推断Key类型,而非同时从flags对象推断,导致flags包含不在keys中的属性时报错。
原错误代码
function createNextFlags<Key extends string>( flags: Record<Key, boolean>, ...keys: [Key, ...Key[]] ) { const nextFlags = { ...flags } for (const key of keys) { nextFlags[key] = true; } return nextFlags; } createNextFlags({ vanilla:false, chocolate:true, // 该行因flags约束过窄产生编译错误 }, "vanilla")
尝试的解决方案
显式定义Flags泛型
尝试引入Flags泛型区分记录类型,但赋值操作触发错误:
// 显式定义Flags function createNextFlags<Flags extends Record<Key, boolean>, Key extends string>( flags: Flags, ...keys: [Key, ...Key[]] ) { const nextFlags = { ...flags } for (const key of keys) { nextFlags[key] = true; // 该赋值操作被判定为非法! } return nextFlags; } createNextFlags({ vanilla:false, chocolate:true, }, "vanilla")
报错信息:
const nextFlags: Flags extends Record<Key, boolean> Type 'boolean' is not assignable to type 'Flags[Key]'
使用keyof推导键
尝试直接用keyof Flags约束keys,仍出现类似错误:
// 使用keyof确保属性名称一致 function createNextFlags<Flags extends Record<any, boolean>>( flags: Flags, ...keys: [keyof Flags, ...(keyof Flags)[]] ) { const nextFlags = { ...flags } for (const key of keys) { nextFlags[key] = true; } return nextFlags; }
报错信息:
const nextFlags: Flags extends Record<any, boolean> Type 'boolean' is not assignable to type 'Flags[keyof Flags]'
使用infer推导键
尝试通过infer提取键类型,错误依旧:
type InferKey<Flags extends Record<any, boolean>> = Flags extends Record<infer Key, boolean> ? Key: never; function createNextFlags<Flags extends Record<any, boolean>>( flags: Flags, ...keys: [InferKey<Flags>, ...InferKey<Flags>[]] ) { const nextFlags = { ...flags } for (const key of keys) { nextFlags[key] = true; } return nextFlags; }
报错信息:
const nextFlags: Flags extends Record<any, boolean> Type 'boolean' is not assignable to type 'Flags[InferKey<Flags>]'
正确解决思路与代码
核心问题是:当Flags为具体对象类型时,TypeScript无法确定Flags[Key]必然是boolean(比如属性可能是true/false字面量类型)。需要明确约束keys来自flags的键,并构造精确的返回类型。
精确实现版本
function createNextFlags<Flags extends Record<string, boolean>, K extends keyof Flags>( flags: Flags, ...keys: K[] ): Omit<Flags, K> & Record<K, true> { const nextFlags = { ...flags } as Omit<Flags, K> & Record<K, true>; for (const key of keys) { nextFlags[key] = true; } return nextFlags; } // 调用示例 const updated = createNextFlags({ vanilla: false, chocolate: true, }, "vanilla"); // updated类型:{ chocolate: boolean; } & { vanilla: true; }
简化实现版本
如果不需要精确的字面量类型,仅保证所有属性为boolean:
function createNextFlags<Flags extends Record<string, boolean>>( flags: Flags, ...keys: (keyof Flags)[] ): Record<keyof Flags, boolean> { const nextFlags = { ...flags } as Record<keyof Flags, boolean>; for (const key of keys) { nextFlags[key] = true; } return nextFlags; }
关键说明
- 泛型
K extends keyof Flags确保keys只能是flags已存在的键,实现keys从flags推断类型的需求 - 通过
Omit<Flags, K> & Record<K, true>构造返回类型,明确标记指定键为true,其余属性保留原类型 - 使用类型断言
as让TypeScript接受赋值操作——我们明确知道将指定键设为true符合返回类型定义
内容的提问来源于stack exchange,提问作者cefn
相关产品推荐
相关产品推荐

