很抱歉标题含糊不清,我花了30分钟的时间思考如何简洁地总结这一点,我想不出比这更好的了。
因此,在去除所有业务逻辑后,我想要实现的情况看起来是这样的:
const Func: <
T extends (PartA & PartB & PartC),
PartA extends Partial<T> = {},
PartB extends Partial<T> = {},
PartC extends Partial<T> = {}
> (parts: { partA: PartA, partB: PartB, partC: PartC }) => T;
本质上,这里的目的是使T
是可选的,这样,如果在没有T
的情况下调用Func()
,则将从PartA
、PartB
和PartC
的结构推断出T
,并且如果传递了T
(作为Func<T>()
(,则它将用于约束PartA
、PartB
和PartC
的类型。
但我当然不能像上面的例子中那样做,因为这会导致T
成为一个循环约束,我想不出任何方法来解决这个问题。但我也知道TS中有各种各样的工具,比如infer
,如果我知道如何正确地实现它们,这些工具可能会很有用。那么,有人知道我可以用什么方法来操纵这些泛型的定义方式吗?
您可以这样更改定义:
const func: <
T extends (A & B & C),
A = unknown,
B = unknown,
C = unknown
> (parts: {
partA: A & Partial<T>,
partB: B & Partial<T>,
partC: C & Partial<T>
}) => T = null!;
三个泛型类型A
、B
和C
获得默认值unknown
,使它们可选。对于part
属性,我们可以使用N & Partial<T>
的交集。这使得可以推断每个part
的泛型类型,但也可以使它们符合Partial<T>
类型。
让我们检查一些测试。
const res1 = func({ partA: { x: "" }, partB: { y: 0 }, partC: { z: new Date() } })
// ^? const res1: { x: string; } & { y: number; } & { z: Date; }
基于输入对象的T
推理似乎如预期的那样工作。
const res2 = func<{ x: string, y: number, z: Date }>({
partA: { x: "0" },
partB: { y: 0 },
partC: { z: new Date() }
})
const res3 = func<{ x: string, y: number, z: Date }>({
partA: { x: "0" },
partB: { y: "0" },
// ^ Error: we violate Partial<T>
partC: { z: new Date() } })
当我们明确地为T
提供类型时,对每个part
强制执行约束Partial<T>
。
唯一需要记住的缺点是以下情况。
const res4 = func<{ x: string, y: number, z: Date }>({
partA: { x: "0" },
partB: { y: "0" },
partC: { z: "0" }
})
每个部分都必须是一个Partial<T>
,但它们不必加起来就是一个完整的T
。它们都可以是空对象,并且TypeScript将被满足。不过,只有在指定了T
的显式类型时,这才是一个问题。
游乐场