TypeScript嵌套属性可变元组返回问题:类型安全合并函数实现
如何实现类型安全的对象嵌套数组合并函数fc?
我想要编写一个名为fc的函数,接收两个对象并以完全类型安全的方式返回它们的合并版本:
const rfc = fc({ num: [1, 2, 3] }, { num: [4, 5, 6] }); // ^? { num: [1, 2, 3, 4, 5, 6] }
平铺场景验证
先在平铺数组场景验证可变元组扩展的有效性:
type t1a = [1, 2, 3] type t2a = [4, 5, 6] type ra = [...t1a, ...t2a] // ^? [1, 2, 3, 4, 5, 6] // 有效,符合预期
再用泛型函数实现平铺数组合并,类型推导正常:
function fa<T1 extends number[], T2 extends number[]>(t1: [...T1], t2: [...T2]): [...T1, ...T2] { return [...t1, ...t2]; } const rfa = fa([1, 2, 3], [4, 5, 6]); // ^? [1, 2, 3, 4, 5, 6] // 有效,符合预期
嵌套场景的问题
尝试直接对嵌套属性的类型进行扩展,结果符合预期:
type t1b = { num: [1, 2, 3] } type t2b = { num: [4, 5, 6] } type rtb = [...t1b['num'], ...t2b['num']] // ^? [1, 2, 3, 4, 5, 6] // 有效,符合预期
但用泛型实现嵌套对象合并时,类型推导不符合预期:
function fc<T1 extends { num: number[] }, T2 extends { num: number[] }>( t1: T1, t2: T2, ): { num: [...T1['num'], ...T2['num']] } { return { num: [...t1.num, ...t2.num] as any }; } const rfc = fc({ num: [1, 2, 3] }, { num: [4, 5, 6] }); // ^? {num: number[]} // 为何不符合预期?不是应该是[1, 2, 3, 4, 5, 6]吗?
问题原因
问题出在泛型约束的T1 extends { num: number[] }和T2 extends { num: number[] }上——当你把T1['num']约束为number[]时,TypeScript会自动将传入的字面量元组[1,2,3]和[4,5,6]拓宽为number[]类型,而非保留它们的具体元组类型信息。这就导致后续的[...T1['num'], ...T2['num']]只能推导为number[],而非预期的合并元组。
正确实现方式
要保留元组的具体类型,需要让泛型参数直接捕获数组的元组类型,而非用number[]约束。修改后的函数如下:
function fc<TNum1 extends number[], TNum2 extends number[]>( t1: { num: [...TNum1] }, t2: { num: [...TNum2] }, ): { num: [...TNum1, ...TNum2] } { return { num: [...t1.num, ...t2.num] }; } const rfc = fc({ num: [1, 2, 3] }, { num: [4, 5, 6] }); // ^? { num: [1, 2, 3, 4, 5, 6] }
扩展:支持任意键的通用合并函数
如果想要支持任意键名的对象嵌套数组合并,可以用更通用的泛型实现:
type MergeNestedArrays<T1, T2> = { [K in keyof T1 & keyof T2]: T1[K] extends any[] ? T2[K] extends any[] ? [...T1[K], ...T2[K]] : T1[K] : T1[K] }; function mergeNestedArrays<T1, T2>(t1: T1, t2: T2): MergeNestedArrays<T1, T2> { const result = {} as MergeNestedArrays<T1, T2>; const keys = new Set([...Object.keys(t1), ...Object.keys(t2)]); for (const key of keys as Array<keyof T1 & keyof T2>) { const val1 = t1[key]; const val2 = t2[key]; if (Array.isArray(val1) && Array.isArray(val2)) { result[key] = [...val1, ...val2] as any; } else { result[key] = val1 as any; } } return result; } // 测试 const merged = mergeNestedArrays( { num: [1,2,3], str: ['a','b'] }, { num: [4,5,6], str: ['c','d'] } ); // ^? { num: [1, 2, 3, 4, 5, 6], str: ["a", "b", "c", "d"] }
内容的提问来源于stack exchange,提问作者Eliav Louski
相关产品推荐
相关产品推荐

