gpt4 book ai didi

typescript - 有没有Partial的替代方法,它仅接受其他类型的字段,而别无其他?

转载 作者:行者123 更新时间:2023-12-03 15:36:05 28 4
gpt4 key购买 nike

给定的接口(interface)或类A和B具有共同的x1字段

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;

You can find the explaination of why this happens here: Why Partial accepts extra properties from another type?



是Partial的替代方法,它仅接受其他类型的字段,而不接受其他字段(尽管不需要所有字段)?像 StrictPartial一样?

这一直在我的代码库中引起很多问题,因为它根本无法检测到错误的类作为参数传递给了函数。

最佳答案

您真正想要的是exact types,其中像“Exact<Partial<A>>”之类的东西会在所有情况下都防止过多的属性。但是TypeScript不直接支持精确类型(至少从TS3.5开始不支持),因此没有好的方法将Exact<>表示为具体类型。您可以将精确类型模拟为通用约束,这意味着突然之间,处理它们的所有内容都需要变为通用而不是具体的。

类型系统唯一将类型视为正确的时间是对“新鲜对象文字”执行excess property checks时,但是在某些极端情况下,这种情况不会发生。这些极端情况之一是当您的类型很弱(没有强制性属性)(例如Partial<A>)时,因此我们完全不能依靠多余的属性检查。

在注释中,您说您想要一个其构造函数采用Exact<Partial<A>>类型的参数的类。就像是

class Example {
constructor(public partialA: Exact<Partial<A>>) {} // doesn't compile
}

我将向您展示如何获得类似的东西,以及一些注意事项。

让我们定义通用类型别名
type Exactly<T, U> = T & Record<Exclude<keyof U, keyof T>, never>;

这采用了 T类型,而我们要确保的候选类型 U是“完全 T”。它返回一个类似于 T的新类型,但具有与 never中的额外属性相对应的额外 U值属性。如果我们将其用作 U的约束(例如 U extends Exactly<T, U>),那么我们可以保证 UT匹配且没有额外的属性。

例如,假设 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但没有额外的属性。

因此,我们需要一个通用的构造函数,例如
class Example {
partialA: Partial<A>;
constructor<T extends Exactly<Partial<A>, T>>(partialA: T) { // doesn't compile
this.partialA = partialA;
}
}

但是您不能这样做,因为构造函数不能在类声明中拥有自己的类型参数。这是泛型类和泛型函数之间的交互的 unfortunate consequence,因此我们将不得不解决它。

这是三种方法。

1:将类设为“不必要的泛型”。这使构造函数可以按需泛型,但是使此类的具体实例带有指定的泛型参数:
class UnnecessarilyGeneric<T extends Exactly<Partial<A>, T>> {
partialA: Partial<A>;
constructor(partialA: T) {
this.partialA = partialA;
}
}
const gGood = new UnnecessarilyGeneric(a); // okay, but "UnnecessarilyGeneric<A>"
const gBad = new UnnecessarilyGeneric(b); // error!
// B is not assignable to {b1: never}

2:隐藏构造函数,并使用静态函数来创建实例。当类不是时,此静态函数可以是通用的:
class ConcreteButPrivateConstructor {
private constructor(public partialA: Partial<A>) {}
public static make<T extends Exactly<Partial<A>, T>>(partialA: T) {
return new ConcreteButPrivateConstructor(partialA);
}
}
const cGood = ConcreteButPrivateConstructor.make(a); // okay
const cBad = ConcreteButPrivateConstructor.make(b); // error!
// B is not assignable to {b1: never}

3:使类不受确切的约束,并为其指定一个虚拟名称。然后使用类型声明从旧的类构造一个新的类构造函数,该构造函数具有所需的通用构造函数签名:
class _ConcreteClassThatGetsRenamedAndAsserted {
constructor(public partialA: Partial<A>) {}
}
interface ConcreteRenamed extends _ConcreteClassThatGetsRenamedAndAsserted {}
const ConcreteRenamed = _ConcreteClassThatGetsRenamedAndAsserted as new <
T extends Exactly<Partial<A>, T>
>(
partialA: T
) => ConcreteRenamed;

const rGood = new ConcreteRenamed(a); // okay
const rBad = new ConcreteRenamed(b); // error!
// B is not assignable to {b1: never}

所有这些都应该工作以接受“确切的” Partial<A>实例,并拒绝具有额外属性的事物。好吧,差不多。

它们拒绝具有已知额外属性的参数。类型系统实际上并不能很好地表示确切类型,因此任何对象都可以具有编译器不知道的额外属性。这是父类(super class)的子类可替换性的本质。如果我可以先做 class X {x: string}然后再做 class Y extends X {y: string},那么 Y的每个实例也是 X的实例,即使 Xy属性一无所知。

因此,您始终可以扩大对象类型以使编译器忽略属性,这是正确的:(过多的属性检查通常会使此操作变得更加困难,但在某些情况下并非如此)
const smuggledOut: Partial<A> = b; // no error

我们知道该编译,并且我无法做任何改变。这意味着即使使用上述实现,您仍然可以在以下位置传递 B:
const oops = new ConcreteRenamed(smuggledOut); // accepted

防止这种情况的唯一方法是使用某种运行时检查(通过检查 Object.keys(smuggledOut)。因此,如果这样的检查确实有害于接受带有额外属性的东西,则最好在类构造函数中构建这样的检查。无论哪种方式,至少在目前为止,上述类定义都可以将类型系统推向精确类型的方向。

希望能有所帮助;祝你好运!

Link to code

关于typescript - 有没有Partial的替代方法,它仅接受其他类型的字段,而别无其他?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56606614/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com