简体   繁体   English

如何定义一个类型并确保它是 typescript 中另一个类型的一部分?

[英]How to define a type and make sure it's part of another type in typescript?

I have a big type:我有一个大类型:

type BigType = {
  aaa: string,
  bbb?: number,
  ccc: boolean[],
  extra?: {
    [key in string]?: string
  },
  nested1: {
    nested2: {
      nested3: {
        [key in string]?: string
      }
    }
  }
}

And I want to define another type and make sure it's subset of the BigType , so I defined a RecursivePartial type:我想定义另一种类型并确保它是BigType的子集,所以我定义了一个RecursivePartial类型:

type RecursivePartial<T> = {
  [P in keyof T]?:
  T[P] extends (infer U)[] ? RecursivePartial<U>[] :
    T[P] extends object ? RecursivePartial<T[P]> :
      T[P];
};

and

type PartOf<T, X extends RecursivePartial<T>> = X;

Now I can define a small type which is only part of the BigType:现在我可以定义一个小类型,它只是 BigType 的一部分:

type SmallType = PartOf<BigType, {
  aaa: string;
  extra: { ddd: string };
  nested1: { nested2: {} }
}>

The problem is I can add properties which is not part of BigType too:问题是我也可以添加不属于BigType的属性:

type SmallType = PartOf<BigType, {
  aaa: string;
  extra: { ddd: string };
  nested1: { nested2: {} },

  someOtherProperty1: string, // not part of BigType
  someOtherProperty2: string, // not part of BigType
}>

How to fix it?如何解决?

The issue here is that object types in TypeScript are open and not exact (see #12936 for discussion about exact types) .这里的问题是 TypeScript 中的 object 类型是开放的并且不准确(有关确切类型的讨论,请参见#12936) That is, you can object types A and B where A extends B and A has properties that B doesn't mention.也就是说,您可以 object 类型AB其中A extends B并且A具有B未提及的属性。 This is actually a crucial part of interface/class hierarchies;这实际上是接口/类层次结构的关键部分; without it, you couldn't add properties to subinterfaces/subclasses.没有它,您将无法向子接口/子类添加属性。 Still, there are times when it surprises people (especially because when you use object literal values the compiler performs additional excess property checking which makes it look like object types are exact).尽管如此,有时它还是会让人们感到惊讶(特别是因为当您使用 object 文字值时,编译器会执行额外的额外属性检查,这使得 object 类型看起来是准确的)。

Exact object types can't currently be represented in TypeScript as specific concrete types.确切的 object 类型目前无法在 TypeScript 中表示为特定的具体类型。 Instead, you have to use generics (see this GitHub comment for more information)相反,您必须使用 generics (有关更多信息,请参阅此 GitHub 评论


Anyway in your case I'd probably proceed by defining a DeepPartial and a DeepNoExcess type alias and using both of them in TypeOf .无论如何,在您的情况下,我可能会继续定义DeepPartialDeepNoExcess类型别名并在TypeOf中使用它们。 DeepPartial looks like this: DeepPartial看起来像这样:

type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };

This is essentially the same as your RecursivePartial , I think.我认为这与您的RecursivePartial基本相同。 As of TS3.1, mapped types automatically map over arrays and tuples without needing special casing, and recursive mapped types that encounter primitive types leave them unmapped (see microsoft/TypeScript#12447 ).从 TS3.1 开始, 映射类型在 arrays 和元组上自动 map 和不需要特殊大小写的元组,遇到原始类型的递归映射类型使它们未映射(参见microsoft/TypeScript#12447 )。 That means you don't need to do much to get a recursive Partial .这意味着您无需做太多事情即可获得递归Partial

DeepNoExcess has to take both the main type and the candidate type (since it's not possible to represent exact types concretely): DeepNoExcess必须同时采用主要类型和候选类型(因为无法具体表示确切的类型):

type DeepNoExcess<T, U> = { [K in keyof U]:
    K extends keyof T ? DeepNoExcess<Required<T>[K], U[K]> :
    never };

This walks down through the properties of the candidate type U and makes the property type never if the property key doesn't also exist in T .这将遍历候选类型U的属性,并且如果属性键也不存在于T中,则使属性类型never出现。 I had to walk down into Required<T> instead of just T because your optional properties weren't being handled properly ( keyof (SomeType | undefined) tends to be never ).我不得不走进Required<T>而不仅仅是T因为您的可选属性没有得到正确处理( keyof (SomeType | undefined)往往是never )。

Then PartOf is defined like this:然后PartOf定义如下:

type PartOf<T, U extends DeepPartial<T> & DeepNoExcess<T, U>> = U;

This yields the behavior you are hoping for with your two examples:这会产生您希望通过两个示例实现的行为:

type GoodSmallType = PartOf<BigType, {
    aaa: string;
    extra: { ddd: string };
    nested1: { nested2: {} }
}>; // okay

type BadSmallType = PartOf<BigType, {
    aaa: string;
    extra: { ddd: string };
    nested1: { nested2: {} },

    someOtherProperty1: string, // not part of BigType
    someOtherProperty2: string, // not part of BigType
}>; // error! Types of property 'someOtherProperty1' are incompatible.

Whether it meets all your use cases is not clear;它是否满足您的所有用例尚不清楚; there are lots of decisions you could make (like Required<T> instead of T ) that will have implications for what types are accepted and what types are not.您可以做出很多决定(例如Required<T>而不是T ),这将对接受哪些类型以及不接受哪些类型产生影响。 But hopefully this gives you a way forward at least.但希望这至少能给你一条前进的道路。 Good luck!祝你好运!

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM