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

标签 typescript partial

给定的接口(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/

相关文章:

javascript - 如何检索 OpenAI 图像并将其保存到 S3 存储桶

javascript - 如何将标记添加到现有语言(例如 typescript )?

elasticsearch - 为所有查询实现ElasticSearch自定义过滤器

Python 请求与 urllib2

C++ 偏特化匹配引用类型

javascript - JS 库的 TypeScript 类型的 API 文档

css - 如何动态更新 Angular 动画? ( Angular 8)

javascript - Angular 中的 HTML 片段在 View 中部分缺失

javascript - Angular , typescript 注释不起作用

Ruby on Rails : load different partial with radio buttons