给定具有公共x1
字段的接口或类A和B
interface A {
a1: number;
x1: number; // <<<<
}
interface B{
b1: number;
x1: number; // <<<<
}
给定实现a和B
let a: A = {a1: 1, x1: 1};
let b: B = {b1: 1, x1: 1};
即使b1不是A的一部分,Typescript也允许这样做:
let partialA: Partial<A> = b;
你可以在这里找到为什么会发生这种情况的解释:Why Partial accepts extra properties from another type?
是否有一种替代Partial的方法,只接受来自另一种类型的字段,而不接受其他任何字段(尽管不需要所有字段)?
这在我的代码库中造成了很多问题,因为它根本无法检测到错误的类作为参数传递给了函数。
2条答案
按热度按时间tjrkku2a1#
您真正需要的是exact types,其中类似于“
Exact<Partial<A>>
“的内容将在所有情况下防止多余的属性。(至少在TS3.5中不是这样),所以没有好的方法将Exact<>
表示为具体类型。您可以将精确类型 * 模拟 * 为泛型约束,这意味着突然之间,与它们有关的一切都需要变得通用而不是具体。类型系统认为类型是精确的唯一情况是对“新鲜对象常量”进行额外的属性检查,但也有一些边缘情况,这种情况不会发生。其中一个边缘情况是当你的类型很弱(没有强制属性),比如
Partial<A>
,所以我们根本不能依赖额外的属性检查。在一个注解中,你说你想要一个类,它的构造函数接受
Exact<Partial<A>>
类型的参数。我将向您展示如何获得这样的东西,沿着一些注意事项。
让我们定义泛型类型别名
这取类型
T
和我们想要确保“确切地为T
“的 * 候选 * 类型U
。它返回类似于T
但具有对应于U
中的额外属性的额外never
值属性的新类型。如果我们将此用作U
上的约束,类似于U extends Exactly<T, U>
,那么我们可以保证U
匹配T
并且没有额外的属性。例如,假设
T
是{a: string}
,U
是{a: string, b: number}
。那么Exactly<T, U>
就等于{a: string, b: never}
。请注意,U extends Exactly<T, U>
为false,因为它们的b
属性不兼容。U extends Exactly<T, U>
为true的唯一方法是U extends T
没有额外的属性。因此我们需要一个 * 泛型 * 构造函数,类似于
但是你不能这样做,因为构造函数不能在类声明中有自己的类型参数,这是泛型类和泛型函数交互的一个不幸的结果,所以我们必须解决它。
这里有三种方法。
1:使类成为“不必要的泛型”。这会使构造函数成为所需的泛型,但会导致该类的具体示例携带指定的泛型参数:
2:隐藏构造函数并使用静态函数来创建示例。此静态函数可以是泛型的,而类不是:
3:创建一个没有确切约束的类,并给予它一个伪名称。然后使用类型Assert从旧的类构造函数创建一个新的类构造函数,它具有你想要的泛型构造函数签名:
所有这些都应该能够接受“精确的”
Partial<A>
示例,并拒绝具有额外属性的内容。它们拒绝具有 known 额外属性的参数。类型系统并没有真正的精确类型的良好表示,因此任何对象都可以具有编译器不知道的额外属性。这是子类对超类的可替换性的本质。如果我可以依次执行
class X {x: string}
和class Y extends X {y: string}
,则Y
的每个示例也是X
的示例,即使X
不知道关于y
属性的任何事情。所以你总是可以扩展一个对象类型,让编译器忽略属性,这是有效的:(在某些情况下,过多的属性检查往往会使这一点变得更加困难,但在这里并非如此)
我们知道编译,我做什么都不能改变这一点,这意味着即使使用上面的实现,您仍然可以将
B
传入:防止这种情况发生的唯一方法是进行某种运行时检查(通过检查
Object.keys(smuggledOut)
)。因此,如果接受具有额外属性的内容确实会造成损害,那么在类构造函数中构建这样的检查是一个好主意。或者,您可以构建类,使其在不受额外属性损害的情况下默默地丢弃这些属性。无论哪种方式,以上的类定义差不多是类型系统可以被推向精确类型的方向的程度,至少现在是这样。希望能有所帮助;祝你好运!
链接到代码
nwnhqdif2#
迟到了,但这对我很有效: