简体   繁体   English

是否有 TypeScript 类型表达式递归要求 object 上的字段?

[英]Is there a TypeScript type expression to recursively require fields on an object?

I have a type like我有一个像

type WeakGuaranteeType = {
  a?: string,
  b?: string,
  c?: string,
  d?: string,
}

But, I want to make a stronger guarantee.但是,我想做一个更有力的保证。 I know that a will always exist when b exists, and b will always exist when c exists, etc. So:我知道当b存在时a将永远存在,当c存在时b将永远存在,等等。所以:

{ a: "" }               // valid
{ a: "", b: "" }        // valid
{ a: "", b: "", c: "" } // valid
{ c: "" }               // not valid!!

Is there a way to describe this in TypeScript?有没有办法在TypeScript中描述这个?

My first attempt was to do:我的第一次尝试是:

type BetterType = 
  | {}
  | ({a: string} & (
      | {}
      | ({b: string} & (
          | {}
          | ({c: string} & (
              // etc...
            ))
        ))
    ))

This is bad because:这很糟糕,因为:

  1. It is very verbose and has to be written out by hand, and它非常冗长,必须手写,并且
  2. It produced an "Expression produces a union type that is too complex to represent" error.它产生了"Expression produces a union type that is too complex to represent"错误。

I think it would be better to use a recursive approach.我认为使用递归方法会更好。

type RecursiveRequire<Keys extends string[]>
  = Keys extends []  // Check if Keys is empty
    ? {}             // Base case. If Keys is empty, "return" {}
    : (              // Recursive case. Add the first key and recurse on the rest
        { [Key in Keys[0]]: string } &
        RecursiveRequire</* What do I put here to say "all of Keys except the first"? */>
      );

type BestType = RecursiveRequire<["a", "b", "c", "d"]>;

You essentially want a type that looks like this:你基本上想要一个看起来像这样的类型:

type BestType = {
    a: string;
    b: string;
    c: string;
    d: string;
} | {
    a: string;
    b: string;
    c: string;
} | {
    a: string;
    b: string;
} | {
    a: string;
}

A union consisting of all valid object types.由所有有效的 object 类型组成的联合体

We can build this union recursively.我们可以递归地建立这个联合。

type RecursiveRequire<Keys extends string[]> = Keys extends [
  ...infer E extends string[],
  infer R extends string
]
  ? { [Key in E[number] | R]: string } | RecursiveRequire<E>
  : never;

type BestType = RecursiveRequire<["a", "b", "c", "d"]>;

This will give us the correct behavior on the tests you specified:这将为我们提供您指定的测试的正确行为:

const a: BestType = { a: "" }; // valid
const b: BestType = { a: "", b: "" }; // valid
const c: BestType = { a: "", b: "", c: "" }; // valid
const d: BestType = { c: "" }; // not valid!!

But there is a catch.但是有一个问题! While our union represents all valid object types, it does not in any way disallow invalid combinations.虽然我们的联合表示所有有效的 object 类型,但它不会以任何方式禁止无效组合。 Remember: excess properties are mostly allowed in TypeScript's structural type system.请记住:在 TypeScript 的结构类型系统中,多余的属性大多是允许的。

So this is not an error:所以这不是错误:

const e = { c: "", a: "" }
const f: BestType = e

To fix this, we have to modify our union to also forbid excess properties.为了解决这个问题,我们必须修改我们的联合以禁止多余的属性。

type RecursiveRequire<
  Keys extends string[], 
  N extends string = never
> = Keys extends [
  ...infer E extends string[],
  infer R extends string
]
  ? (({ 
      [Key in E[number] | R]: string 
    } & { 
      [K in N]?: never
    }) | RecursiveRequire<E, R | N>) extends infer U ? { 
      [K in keyof U]: U[K] 
    } : never
  : never;

type BestType = RecursiveRequire<["a", "b", "c", "d"]>;

BestType will now look like this: BestType现在看起来像这样:

type BestType = {
    a: string;
    b: string;
    c: string;
    d: string;
} | {
    a: string;
    b: string;
    c: string;
    d?: undefined;
} | {
    a: string;
    b: string;
    c?: undefined;
    d?: undefined;
} | {
    a: string;
    b?: undefined;
    c?: undefined;
    d?: undefined;
}

This now passes our excess property test case.这现在通过了我们的多余属性测试用例。

const a: BestType = { a: "" }; // valid
const b: BestType = { a: "", b: "" }; // valid
const c: BestType = { a: "", b: "", c: "" }; // valid
const d: BestType = { c: "" }; // not valid!!

const e = { c: "", a: "" }
const f: BestType = e // not valid!!

Playground 操场

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

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