从概念上讲,这实际上是两个截然不同的功能:
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
,并根据结果委托calculateObjSum
或calculateNumSum
的实现.
请注意,与两个函数版本相比,它的类型安全性稍差一些,因为TypeScrip无法分析实现是否实际分别满足每个调用签名.有关详细信息,请参阅microsoft/TypeScript#13235.这意味着您必须小心不要搞砸了实现.
一个单独的calculateSum
是否比一对calculateObjSum
和calculateNumSum
函数更好,归根结底是主观的.我的意见是,最好有两个不同的职能,每个职能都有明确的单一责任.单函数版本给人的感觉就像两个函数被挤进了一个容器.这使得类型脚本编译器和函数实现者都更难进行推理.我希望来电者也能得到同样的评价.但这取决于你.
当然,可以决定将这两个函数都视为更一般函数的特定实例,该函数将对应于path的variadic个属性键带入数组元素.因此,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个