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

如何为自定义TypeScript Reduce函数实现条件类型以消除类型错误

自定义Array.reduce函数的TypeScript类型错误解决

问题描述

我实现了一个独立于全局Array的自定义Array.prototype.reduce函数,定义了三个重载签名:

function reduce<T> (
  arr: T[],
  reduceFunction: (acc: T, item: T, index: number, arr: T[]) => T,
): T;

function reduce<T> (
  arr: T[],
  reduceFunction: (acc: T, item: T, index: number, arr: T[]) => T,
  initialValue: T,
): T;

function reduce<T, R> (
  arr: T[],
  reduceFunction: (acc: R, item: T, index: number, arr: T[]) => R,
  initialValue?: R
): R;

但编写函数体时,prev变量出现类型赋值错误,需要对initialValue和acc参数进行条件类型定义来消除这些错误。

函数体代码及错误提示如下:

function reduce<T> (
  arr: T[],
  reduceFunction: (acc: T, item: T, index: number, arr: T[]) => T,
): T;

function reduce<T> (
  arr: T[],
  reduceFunction: (acc: T, item: T, index: number, arr: T[]) => T,
  initialValue: T,
): T;

function reduce<T, R> (
  arr: T[],
  reduceFunction: (acc: R, item: T, index: number, arr: T[]) => R,
  initialValue?: R
): R;

function reduce<T, R> (
  arr: T[],
  reduceFunction: (acc: R | T, item: T, index: number, arr: T[]) => R | T,
  initialValue?: R | T
): R | T {
  if (initialValue === undefined) {
    let prev = arr[0]

    for (let i = 1; i < arr.length; i++) {
      const item = arr[i]
      // Type 'T | R' is not assignable to type 'T'.'T' could be instantiated with an arbitrary type which could be unrelated to 'T | R'.ts(2322)
      prev = reduceFunction(prev, item, i, arr)
    }

    return prev
  } else {
    let prev = initialValue as R

    for (let i = 0; i < arr.length; i++) {
      const item = arr[i]
      
      // let prev: (T & ({} | null)) | (R & ({} | null))
      // Type 'T | R' is not assignable to type '(T & ({} | null)) | (R & ({} | null))'.
      // Type 'T' is not assignable to type '(T & ({} | null)) | (R & ({} | null))'.
      // Type 'T' is not assignable to type 'T & null'.
      // Type 'T' is not assignable to type 'null'.ts(2322)
      prev = reduceFunction(prev, item, i, arr) as R
    }

    return prev
  }
}

// 使用示例
interface User {
  name: string;
  surname: string;
}

interface UserWithFullname extends User {
  fullname: string;
}

const arr: User[] = [{ name: "John", surname: "Doe" }];
const products = [{ capacity: 2, price: 10 }];

const result = reduce<User, UserWithFullname[]>(
  arr,
  (acc, item) => {
    return [...acc, { ...item, fullname: item.name + ' ' + item.surname }]
  },
  []
)

const total = reduce(
  products,
  (acc, item) => {
    return acc + item.capacity * item.price
  },
  0
)

console.log(result)
console.log(total)

解决方法

核心问题是函数实现的泛型定义没有区分有无初始值的两种场景,导致TypeScript无法准确推断acc的类型。我们可以通过条件类型和重载签名的精准匹配来修复:

步骤1:重构泛型参数,区分返回值类型

将实现的泛型改为根据initialValue是否存在来决定返回值类型,使用条件类型R extends undefined ? T : R来明确类型分支。

步骤2:修正函数实现的类型定义

把函数实现的reduceFunction类型拆分为两种情况:

  • 无初始值时,acc的初始类型是T,每次返回值也是T
  • 有初始值时,acc的类型是R,每次返回值也是R

最终修复后的代码

function reduce<T> (
  arr: T[],
  reduceFunction: (acc: T, item: T, index: number, arr: T[]) => T,
): T;

function reduce<T> (
  arr: T[],
  reduceFunction: (acc: T, item: T, index: number, arr: T[]) => T,
  initialValue: T,
): T;

function reduce<T, R> (
  arr: T[],
  reduceFunction: (acc: R, item: T, index: number, arr: T[]) => R,
  initialValue: R
): R;

function reduce<T, R = T>(
  arr: T[],
  reduceFunction: 
    | ((acc: T, item: T, index: number, arr: T[]) => T)
    | ((acc: R, item: T, index: number, arr: T[]) => R),
  initialValue?: R
): R extends undefined ? T : R {
  if (initialValue === undefined) {
    // 无初始值场景:acc类型固定为T
    let prev = arr[0] as T;
    if (prev === undefined) throw new Error("Reduce of empty array with no initial value");

    for (let i = 1; i < arr.length; i++) {
      prev = (reduceFunction as (acc: T, item: T, index: number, arr: T[]) => T)(prev, arr[i], i, arr);
    }

    return prev as R extends undefined ? T : R;
  } else {
    // 有初始值场景:acc类型固定为R
    let prev = initialValue as R;

    for (let i = 0; i < arr.length; i++) {
      prev = (reduceFunction as (acc: R, item: T, index: number, arr: T[]) => R)(prev, arr[i], i, arr);
    }

    return prev as R extends undefined ? T : R;
  }
}

// 使用示例
interface User {
  name: string;
  surname: string;
}

interface UserWithFullname extends User {
  fullname: string;
}

const arr: User[] = [{ name: "John", surname: "Doe" }];
const products = [{ capacity: 2, price: 10 }];

const result = reduce<User, UserWithFullname[]>(
  arr,
  (acc, item) => {
    return [...acc, { ...item, fullname: item.name + ' ' + item.surname }]
  },
  []
)

const total = reduce(
  products,
  (acc, item) => {
    return acc + item.capacity * item.price
  },
  0
)

console.log(result)
console.log(total)

关键说明

  1. 重载签名的精准性:新增了第三个重载(明确initialValue为必填的R类型),让TypeScript在调用时能更准确地匹配场景。
  2. 类型断言的合理使用:在函数体内,根据initialValue的存在情况,将reduceFunction断言为对应的类型分支,避免类型混淆。
  3. 条件类型返回值:使用R extends undefined ? T : R让返回值类型根据是否传入初始值自动切换,符合Array.reduce的原生行为。

内容的提问来源于stack exchange,提问作者Ansagan Islamgali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 21:40:20