[英]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:这很糟糕,因为:
"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!!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.