如何定义类型并确保它是打字稿中另一种类型的一部分?

问题描述 投票:1回答:1

我有一个大字型:

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

而且我想定义另一种类型并确保它是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];
};

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

现在我可以定义一个小的类型,它只是BigType的一部分:

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

问题是我也可以添加不属于BigType的属性:

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

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

如何解决?

typescript typing
1个回答
0
投票

这里的问题是TypeScript中的对象类型是开放的,而不是exact (see #12936 for discussion about exact types)。也就是说,您可以对象类型AB,其中A extends BA具有B没有提及的属性。这实际上是接口/类层次结构的关键部分。没有它,就无法将属性添加到子接口/子类。尽管如此,有时候它还是让人感到惊讶(特别是因为当您使用对象文字值时,编译器会执行附加的excess property checking,这使对象类型看起来像是精确的)。

确切的对象类型当前无法在TypeScript中表示为特定的具体类型。相反,您必须使用泛型(有关更多信息,请参见this GitHub comment


无论如何,我可能会先定义DeepPartialDeepNoExcess类型别名,然后在TypeOf中使用它们。 DeepPartial看起来像这样:

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

我认为这与您的RecursivePartial基本相同。从TS3.1开始,mapped types automatically map over arrays and tuples不需要特殊的大小写,并且遇到原始类型的递归映射类型将它们保留为未映射状态(请参见microsoft/TypeScript#12447)。这意味着您不需要做很多事情就可以获得递归Partial

[DeepNoExcess必须同时采用主类型和候选类型(因为不可能具体表示精确类型):

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

这会遍历候选类型U的属性,如果never中也不存在属性键,则使属性类型为T。我不得不走进Required<T>而不是T,因为您的可选属性未得到正确处理(keyof (SomeType | undefined)往往是never)。

然后PartOf的定义如下:

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

这将产生您希望通过两个示例获得的行为:

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.

它是否满足您的所有用例尚不清楚;您可以做出很多决定(例如Required<T>而不是T),这些决定将影响接受什么类型和不接受什么类型。但是希望这至少可以给您前进的道路。祝你好运!

Playground link to code

© www.soinside.com 2019 - 2024. All rights reserved.