TypeScript泛型类型手动传入与自动推断问题及数组生成器优化
TypeScript简易数组生成器的类型定义优化
问题背景
需要为一个简易数组生成器添加TypeScript类型定义,要求同时支持类型自动推断和手动指定类型。原始JavaScript函数如下:
function generateArr(count, init) { if (typeof init === 'function') { return new Array(count).fill(undefined).map((_value, index) => init(index)) } else { return new Array(count).fill(init) } }
初始重载实现的问题
最初编写的重载版本无法适配所有场景,代码示例:
function generateArr<T extends (index: number) => any>(count: number, init: T): ReturnType<T>[]; function generateArr<T>(count: number, init: T): T[]; function generateArr(count: number, init: unknown): unknown[] { if (typeof init === 'function') { return new Array(count).fill(undefined).map((_value, index) => init(index)) } else { return new Array(count).fill(init) } } // 正常场景 const a1 = generateArr(2, 'a') // ^? string[] const a2 = generateArr<'a' | 's'>(2, 'a') // ^? ('a' | 's')[] const a3 = generateArr(2, () => 's' as 'a' | 's') // ^? ('a' | 's')[] const a4 = generateArr(2, {a :4}) // ^? { a: number }[] // 错误场景:手动指定类型时,函数参数不匹配 const a5 = generateArr<'a' | 's'>(2, () => 's') // ERROR Argument of type '() => string' is not assignable to parameter of type '"a" | "s"'.(2345)
此处问题在于,手动指定泛型T时,TypeScript会匹配第二个重载,要求init的类型必须是T,但实际期望的是当init为函数时,其返回值符合T即可。
现有NoInfer解决方案
使用NoInfer解决了问题,但实现相对复杂:
function generateArr<T extends (index: number) => any>(count: number, init: T): ReturnType<T>[]; function generateArr<T>(count: number, init: NoInfer<(index: number) => T>): T[] function generateArr<T>(count: number, init: T): T[]; function generateArr(count: number, init: unknown): unknown[] { if (typeof init === 'function') { return new Array(count).fill(undefined).map((_value, index) => init(index)) } else { return new Array(count).fill(init) } }
优化疑问
是否存在更简洁的实现方式?是否需要区分泛型T是手动传入还是自动推断来编写不同的重载?
回答
可以通过合并重载逻辑,利用条件类型和泛型约束简化实现,无需刻意区分手动/自动推断场景:
方案一:条件类型整合版
function generateArr<T>( count: number, init: T extends (index: number) => infer R ? T : T | ((index: number) => T) ): T extends (index: number) => infer R ? R[] : T[]; function generateArr(count: number, init: unknown): unknown[] { if (typeof init === 'function') { return new Array(count).fill(undefined).map((_value, index) => (init as (index: number) => unknown)(index)) } else { return new Array(count).fill(init) } }
验证场景
// 自动推断:非函数参数 const a1 = generateArr(2, 'a') // ^? string[] // 手动指定类型:非函数参数适配 const a2 = generateArr<'a' | 's'>(2, 'a') // ^? ('a' | 's')[] // 自动推断:函数参数返回值类型推断 const a3 = generateArr(2, () => 's' as 'a' | 's') // ^? ('a' | 's')[] // 自动推断:对象参数 const a4 = generateArr(2, {a :4}) // ^? { a: number }[] // 手动指定类型:函数参数返回值符合指定类型即可 const a5 = generateArr<'a' | 's'>(2, () => 's') // ^? ('a' | 's')[] 无错误
原理说明
- 泛型
T支持自动推断与手动指定两种模式 - 通过条件类型
T extends (index: number) => infer R判断init类型:- 若
init是函数,直接使用原函数类型,返回值为R[] - 若
init非函数,允许其为T本身或返回T的函数,返回值为T[]
- 若
- 该逻辑让TypeScript自动匹配类型规则,避免了
NoInfer带来的复杂度
方案二:简化重载版
// 自动推断:init是函数,返回ReturnType<T>[] function generateArr<T extends (index: number) => any>(count: number, init: T): ReturnType<T>[]; // 手动指定类型:init可以是T或者返回T的函数 function generateArr<T>(count: number, init: T | ((index: number) => T)): T[]; // 实现 function generateArr(count: number, init: unknown): unknown[] { if (typeof init === 'function') { return new Array(count).fill(undefined).map((_value, index) => (init as (index: number) => unknown)(index)) } else { return new Array(count).fill(init) } }
这个版本更直观,同样覆盖所有场景:
- 第一个重载处理自动推断的函数场景
- 第二个重载同时处理手动指定类型的函数/非函数场景,以及自动推断的非函数场景
无需刻意区分泛型是手动传入还是自动推断,TypeScript的重载匹配机制会根据参数和泛型指定情况自动选择最合适的重载。
内容的提问来源于stack exchange,提问作者skyboyer
相关产品推荐
相关产品推荐

