You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 05:17:22