TypeScript中如何修改泛型键对应的数组类型属性?
解决TypeScript泛型函数仅支持对象中string[]类型属性的类型推断问题
问题场景
需要编写一个泛型函数,仅允许操作对象中类型为string[](或其字面量数组子类型)的属性。示例类型如下:
const thing2 = ['!', '@', '#'] as const; type Thing2 = typeof thing2[number]; const thing3 = ['1', '2', '3'] as const; type Thing3 = typeof thing3[number]; type Thing = { a: Thing2[], b: Thing3[], c: number, }
该函数仅应接受a和b作为键参数。
当前代码及问题
现有代码无法让TypeScript识别obj[key]为数组类型,报错Property 'push' does not exist on type 'O[K]',且希望尽量避免使用类型断言as:
type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T]; function append<O extends object, K extends KeysMatching<O, string[]>>( obj: O, key: K, item: O[K] extends (infer U)[] ? U : never ) { obj[key].push(item); // Property 'push' does not exist on type 'O[K]' }
解决方案
问题核心是原约束仅限定了键的范围,未明确关联键对应值的数组类型。以下两种方案可解决该问题:
方案1:优化泛型约束,明确值类型关联
type KeysMatching<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T]; function append<O extends object, K extends KeysMatching<O, readonly string[]>>( obj: O & Record<K, string[]>, // 明确指定O[K]为数组类型 key: K, item: O[K] extends (infer U)[] ? U : never ) { obj[key].push(item); // 类型推断正常 } // 测试验证 const thing: Thing = { a: [], b: [], c: 1 }; append(thing, 'a', '!'); // 合法 append(thing, 'b', '1'); // 合法 // append(thing, 'c', 'x'); // 报错,符合预期
通过O & Record<K, string[]>给obj添加精确类型约束,让TypeScript明确obj[key]是带有push方法的数组类型。
方案2:内联约束,简化泛型逻辑
直接在泛型中约束O[K]为数组类型,省去KeysMatching的间接推导:
function append<O extends object, K extends keyof O>( obj: O, key: K extends (O[K] extends readonly string[] ? K : never), item: O[K] extends (infer U)[] ? U : never ) { obj[key].push(item); } // 测试验证 const thing: Thing = { a: [], b: [], c: 1 }; append(thing, 'a', '!'); append(thing, 'b', '1'); // append(thing, 'c', 'x'); // 报错,符合预期
这种方式将KeysMatching的逻辑内联到泛型约束中,缩短类型推断链条,让编译器直接识别obj[key]的数组属性。
原理说明
TypeScript泛型推断需要明确的类型关联,原代码仅限定了键的范围,未建立键与值类型的直接绑定。通过交叉类型或内联约束,我们让编译器明确知道K对应的O[K]是数组类型,从而正确识别push方法。
内容的提问来源于stack exchange,提问作者Procyon
相关产品推荐
相关产品推荐

