Typescript双向推理/约束



很抱歉标题含糊不清,我花了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(),则将从PartAPartBPartC的结构推断出T,并且如果传递了T(作为Func<T>()(,则它将用于约束PartAPartBPartC的类型。

但我当然不能像上面的例子中那样做,因为这会导致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!;

三个泛型类型ABC获得默认值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的显式类型时,这才是一个问题。


游乐场

最新更新