TypeScript泛型元组创建对象有自动补全,访问属性却报错?
TypeScript泛型元组合并类型报错分析与解决
问题复现
你尝试通过泛型元组合并多个对象类型,但访问属性时出现类型错误,代码如下:
const foo = { user: { id: 1, }, }; const bar = { hello: "goodbye", }; type Test<T extends Record<PropertyKey, any>[]> = ( T[number] extends any ? (k: T[number]) => void : never ) extends (k: infer I) => void ? I : never; const test: Test<[typeof foo, typeof bar]> = { user: { id: 1 }, hello: "23", }; console.log(test.user); // 类型“{ user: { id: number; }; } | { hello: string; }”上不存在属性“user”。 // 类型“{ hello: string; }”上不存在属性“user”。ts(2339)
问题原因
你的Test类型误用了分布式条件类型,最终生成的是元组元素类型的联合类型,而非期望的交叉类型:
T[number]展开为元组所有元素的联合类型(typeof foo | typeof bar);- 条件类型左侧是“裸类型参数”时,TypeScript会触发分布式逻辑,将联合中的每个成员单独代入条件,生成
((k: typeof foo) => void) | ((k: typeof bar) => void)的函数联合类型; - 后续通过
infer I推导时,得到的I是原联合类型typeof foo | typeof bar。
当test的类型为联合类型时,访问user属性会报错——联合类型的类型检查要求所有成员都拥有该属性才能安全访问,而typeof bar并没有user属性。
至于创建对象时能自动补全,是因为TypeScript允许包含联合所有成员属性的对象赋值给联合类型(该对象是联合类型的子类型)。
解决方案
方案1:递归交叉元组元素(直观易懂)
直接递归遍历元组,将每个元素的类型交叉合并:
const foo = { user: { id: 1, }, }; const bar = { hello: "goodbye", }; // 递归合并元组元素为交叉类型 type MergeTuple<T extends any[]> = T extends [infer First, ...infer Rest] ? First & MergeTuple<Rest> : {}; const test: MergeTuple<[typeof foo, typeof bar]> = { user: { id: 1 }, hello: "23", }; console.log(test.user); // 正常访问,类型为{ id: number } console.log(test.hello); // 正常访问,类型为string
方案2:修改原Test类型,避免分布式条件类型
通过将T[number]包裹在数组中阻止分布式逻辑,同时利用函数参数的逆变特性推导交叉类型:
const foo = { user: { id: 1, }, }; const bar = { hello: "goodbye", }; type Test<T extends Record<PropertyKey, any>[]> = ( // 用[]包裹T[number],避免触发分布式条件类型 [T[number]] extends [infer U] ? (k: U) => void : never ) extends (k: infer I) => void ? I : never; const test: Test<[typeof foo, typeof bar]> = { user: { id: 1 }, hello: "23", }; console.log(test.user); // 无报错
内容的提问来源于stack exchange,提问作者Jonathon Orsi
相关产品推荐
相关产品推荐

