我有一个简单的现有方法,可以通过其中一个属性来总结对象array.

const calculateSum = <T extends object, K extends keyof T>(array: T[], property : K) : number =>{
  
  let total = 0;
  if (property)
  {
      total = array.reduce((acc, item) => {
      return acc + (+item[property]);
    }, 0);

  }
  return total;
}

如何重构此方法,以便也可以传递数组number[],并在本例中将其相加.在这种情况下如何不要求property及格?

推荐答案

从概念上讲,这实际上是两个截然不同的功能:

function calculateObjSum<K extends PropertyKey>(
    arr: (Record<string, any> & Partial<Record<K, number>>)[],
    property: K
): number {
    const _arr: Partial<Record<K, number>>[] = arr;
    return _arr.reduce((a, v) => a + (v[property] ?? 0), 0)
}
const s1 = calculateObjSum([{ a: "abc", b: 123 }, { a: "def", b: 456 }], "b");
console.log(s1) // 579

function calculateNumSum(arr: number[]): number {
    return arr.reduce((a, v) => a + v, 0)
}
const s2 = calculateNumSum([789, 123, 456]);
console.log(s2) // 1368

在某种程度上,calculateObjSum()函数是您的示例的类型安全版本,因为它只允许输入arr‘S元素在property键处有number个属性的输入.而calculateNumSum()只是直接将一组数字相加.


现在,如果您真的想将这些合并到单个函数中,则必须设置类似于多个调用签名的东西(以使TypeScrip满意)和多个实现(以使函数在运行时工作).例如,我们可以编写以下overloaded个函数:

// call signatures
function calculateSum<K extends PropertyKey>(
    arr: (Record<string, any> & Partial<Record<K, number>>)[],
    property: K
): number;
function calculateSum(arr: number[]): number;

// implementation
function calculateSum(arr: any[], property?: PropertyKey) {
    if (typeof property === "string") {
        return arr.reduce((a, v) => a + (v[property] ?? 0), 0);
    }
    return arr.reduce((a, v) => a + v, 0);
}

const s1 = calculateSum([{ a: "abc", b: 123 }, { a: "def", b: 456 }], "b");
console.log(s1) // 579

const s2 = calculateSum([789, 123, 456]);
console.log(s2) // 1368

这起到了预期的作用.该实现判断property是否是string而不是undefined,并根据结果委托calculateObjSumcalculateNumSum的实现.

请注意,与两个函数版本相比,它的类型安全性稍差一些,因为TypeScrip无法分析实现是否实际分别满足每个调用签名.有关详细信息,请参阅microsoft/TypeScript#13235.这意味着您必须小心不要搞砸了实现.

一个单独的calculateSum是否比一对calculateObjSumcalculateNumSum函数更好,归根结底是主观的.我的意见是,最好有两个不同的职能,每个职能都有明确的单一责任.单函数版本给人的感觉就像两个函数被挤进了一个容器.这使得类型脚本编译器和函数实现者都更难进行推理.我希望来电者也能得到同样的评价.但这取决于你.


当然,可以决定将这两个函数都视为更一般函数的特定实例,该函数将对应于pathvariadic个属性键带入数组元素.因此,calculateSum(arr)要求arr‘S元素为number,calculateSum(arr, "a")要求元素为{a?: number},calculateSum(arr, "a", "b", "c")要求元素为{a?: {b?: {c?: number}}}.如果是这样的话,您实际上可以将其编写为单个函数,可能如下所示:

type DeepNumberDict<K extends PropertyKey[]> =
    K extends [infer K0 extends PropertyKey, ...infer KR extends PropertyKey[]] ?
    Record<string, any> & { [P in K0]?: DeepNumberDict<KR> } : number;

function calculateSum<K extends PropertyKey[]>(
    arr: DeepNumberDict<K>[], ...keys: K
): number {
    return arr.reduce((a, v) => a + ((keys.reduce((a, k) => (a ?? {})[k], v as any)) ?? 0), 0);
}

你可以看到它是有效的:

const s1 = calculateSum([{ a: "abc", b: 123 }, { a: "def", b: 456 }], "b");
console.log(s1) // 579

const s2 = calculateSum([789, 123, 456]);
console.log(s2) // 1368

const s3 = calculateSum([{ a: { b: { c: 1 } } }, { a: { b: { c: 2 } } }], "a", "b", "c");
console.log(s3) // 3

同样,不确定是否值得.函数的行为对于TypeScript来说太复杂了,无法验证(所以我们需要类型断言和/或any),而DeepNumberDict实用程序类型对于普通用户来说可能太难理解了. 但是,至少在概念上,它是一个单一的东西.

Playground link to code

Typescript相关问答推荐

在Angular中,如何在文件上传后清除文件上传文本框

为什么typescript要把这个空合并转换成三元?

React typescribe Jest调用函数

无法正确推断嵌套泛型?

类型脚本中没有接口的中间静态类

编剧错误:正在等待Expect(Locator).toBeVisible()

Angular NgModel不更新Typescript

如何使用Geojson模块和@Types/Geojson类型解析TypeScrip中的GeoJSON?

如何将v-for中的迭代器编写为v-if中的字符串?

TypeError:正文不可用-NextJS服务器操作POST

如何将父属性类型判断传播到子属性

如何过滤文字中的类对象联合类型?

如何在使用条件类型时使用void

判断映射类型内的键值的条件类型

如何将类似数组的对象(字符串::匹配结果)转换为类型脚本中的数组?

在单独的组件中定义React Router路由

两个名称不同的相同打字界面-如何使其干燥/避免重复?

为什么我会得到一个;并不是所有的代码路径都返回一个值0;switch 中的所有情况何时返回或抛出?(来自vsCode/TypeScript)

无法使用prisma中的事务更新记录

使用嵌套属性和动态执行时,Typescript 给出交集而不是并集