TypeScript 如何实现magic函数返回实例元组而非联合数组
magic 函数元组返回类型实现方案
原实现的类型问题根因
原代码中magic的参数定义为Constructor[],本质是所有构造函数组成的同类型数组,数组内每个元素没有和自身的实例类型做独立绑定,TS只能统一将所有元素推断为宽泛的Constructor类型,最终返回值自然会被推断为(Foo | Bar)[],解构时无法拿到固定位置、固定元素类型的元组。
改造核心思路
利用TypeScript对函数参数的逐位泛型推断能力,将原本“统一类型的数组参数”改为逐位关联构造函数与对应实例的泛型元组结构:
- 让泛型参数动态捕获传入的所有构造函数对应的实例类型,组成一个长度、元素位置完全匹配传入参数的元组
- 反向约束每个位置的入参,必须是能生成对应位置实例类型的构造函数
- 函数返回值直接使用这个捕获到的实例元组类型,从根源上避免联合类型数组的推断结果
完整实现
兼容原代码单数组传参的写法
匹配原代码中magic([Foo, Bar])的调用形式
type Constructor<T = {}> = new (...args: any[]) => T class Foo {} class Bar {} function magic<T extends unknown[]>( // 逐位匹配入参:第K位的参数,是能生成T[K]类型实例的构造函数 types: { [K in keyof T]: Constructor<T[K]> } ): T { // 运行时逻辑非常直接:遍历每个构造函数生成实例,收集为数组返回 return types.map(Type => new Type()) as T } // 调用测试 const [foo, bar] = magic([Foo, Bar]) // TS自动推断返回值类型为[Foo, Bar],foo为Foo类型,bar为Bar类型
支持构造函数展开传参的写法
即支持magic(Foo, Bar)的可变参数调用形式
function magic<T extends unknown[]>( ...types: { [K in keyof T]: Constructor<T[K]> } ): T { return types.map(Type => new Type()) as T } // 调用测试 const [foo, bar] = magic(Foo, Bar) // 返回值类型同样为[Foo, Bar]
对任意数量、任意顺序构造函数的支持说明
上述实现完全没有固定元组长度,泛型T是动态适配的可变元组:
- 无论传入多少个构造函数,TS都会逐位捕获每个构造函数对应的实例类型,生成和入参长度完全一致的返回元组
- 无论构造函数的传入顺序如何,返回值的元素类型都会严格和入参位置一一对应,不会出现顺序错乱
举个验证例子:
class Baz {} // 传入3个不同顺序、不同类型的构造函数 const [a, b, c] = magic([Bar, Baz, Foo]) // TS自动推断返回值类型为[Bar, Baz, Foo] // 其中a是Bar类型,b是Baz类型,c是Foo类型,完全匹配入参顺序
注:如果需要支持构造函数带初始化参数的场景,只要保留
Constructor类型定义中的...args: any[]参数位即可,现有类型推断逻辑不会受影响。
内容的提问来源于stack exchange,提问作者ldlework
相关产品推荐
相关产品推荐

