Typescript 类型,其中对象仅由一组可能属性中的单个属性组成

Seb*_*yra 10 typescript

我正在尝试使用类型系统对特定模式进行建模,其中对象仅由一组可能属性中的单个属性组成。

换句话说,该类型将是部分类型,但只允许一个属性。

interface PossibleProperties {
  cat?: AllPropsOfSameType;
  dog?: AllPropsOfSameType;
  cow?: AllPropsOfSameType;
}

interface ShouldBeExactlyOneOfPossibleProperties {
  [P in keyof PossibleProperties]: AllPropsOfSameType; // Wupz, this allows for 0 or more...
}
Run Code Online (Sandbox Code Playgroud)

我见过需要至少一个属性的解决方案:

type AtLeastOne<T, U = {[K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]
Run Code Online (Sandbox Code Playgroud)

但我需要类似AtMostOne<T, U = {[K in keyof T]: Pick<T, K> }>or 的东西ExactlyOne<T, U = {[K in keyof T]: Pick<T, K> }>,它可能是AtMostOneand的交集类型AtLeastOne

如果这可能的话有什么想法吗?

小智 9

使用整篇文章中的信息,我得到以下解决方案:

type Explode<T> = keyof T extends infer K
  ? K extends unknown
  ? { [I in keyof T]: I extends K ? T[I] : never }
  : never
  : never;
type AtMostOne<T> = Explode<Partial<T>>;
type AtLeastOne<T, U = {[K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]
type ExactlyOne<T> = AtMostOne<T> & AtLeastOne<T>
Run Code Online (Sandbox Code Playgroud)


Val*_*kov 1

我的第一个想法是创建一个联合类型,例如:

type ExactlyOne =
  { cat?: AllPropsOfSameType } |
  { dog?: AllPropsOfSameType } |
  { cow?: AllPropsOfSameType };
Run Code Online (Sandbox Code Playgroud)

可以使用分布式条件类型

type ExactlyOne<T, TKey = keyof T> = TKey extends keyof T ? { [key in TKey]: T[TKey] } : never;
type ShouldBeExactlyOneOfPossibleProperties = ExactlyOne<PossibleProperties>;
Run Code Online (Sandbox Code Playgroud)

操场

但它仍然允许分配具有多个属性的对象:

// this assignment gives no errors
const animal: ShouldBeExactlyOneOfPossibleProperties = {
  cat: 'a big cat',
  dog: 'a small dog'
};
Run Code Online (Sandbox Code Playgroud)

这是因为 TypeScript 中的联合类型是包容性的,目前您无法创建独占联合类型。看到这个答案

所以我们需要以某种方式禁止额外的属性。一个选项可能是使用nevertype,但不幸的是,不可能创建 type 的可选属性,never因为never | undefined给出undefined. 如果可以使用undefined其他属性,您可以使用以下可怕的类型:

type ExactlyOne<T, TKey = keyof T> = TKey extends keyof T
  ? { [key in Exclude<keyof T, TKey>]?: never } & { [key in TKey]: T[key] }
  : never;
Run Code Online (Sandbox Code Playgroud)

结果类型如下所示:

({
    dog?: undefined;
    cow?: undefined;
} & {
    cat: string | undefined;
}) | ({
    cat?: undefined;
    cow?: undefined;
} & {
    dog: string | undefined;
}) | ({
    cat?: undefined;
    dog?: undefined;
} & {
    cow: string | undefined;
})
Run Code Online (Sandbox Code Playgroud)

太可怕了……但已经接近预期了。

操场

这种方法的缺点是,如果您尝试分配具有多个属性的对象,则会出现不具描述性的错误消息,例如以下分配:

const animal: ShouldBeExactlyOneOfPossibleProperties = {
  cat: 'a big cat',
  dog: 'a small dog'
};
Run Code Online (Sandbox Code Playgroud)

给出以下错误:

Type '{ cat: string; dog: string; }' is not assignable to type '({ dog?: undefined; cow?: undefined; } & { cat: string | undefined; }) | ({ cat?: undefined; cow?: undefined; } & { dog: string | undefined; }) | ({ cat?: undefined; dog?: undefined; } & { cow: string | undefined; })'.
  Type '{ cat: string; dog: string; }' is not assignable to type '{ cat?: undefined; dog?: undefined; } & { cow: string | undefined; }'.
    Type '{ cat: string; dog: string; }' is not assignable to type '{ cat?: undefined; dog?: undefined; }'.
      Types of property 'cat' are incompatible.
        Type 'string' is not assignable to type 'undefined'.(2322)
Run Code Online (Sandbox Code Playgroud)

另一种方法:您可以模拟此答案中建议的独占联合类型。但在这种情况下,一个额外的属性被添加到对象中。

type ExactlyOne<T, TKey = keyof T> = TKey extends keyof T
  ? { [key in TKey]: T[TKey] } & { prop: TKey }
  : never;

const animal: ExactlyOne<PossibleProperties> = {
  prop: 'cat',
  cat: 'a big cat'
};
Run Code Online (Sandbox Code Playgroud)

操场