如何限制数组在 TypeScript 中包含枚举的每个成员
How to restrict an array to have every member of an enum in TypeScript
enum AllowedFruits {
Apple = 'APPLE',
Banana = 'BANANA',
Pear = 'PEAR'
}
const allowedFruits: AllowedFruits[] = [
AllowedFruits.Apple, AllowedFruits.Banana, AllowedFruits.Pear
]
我想要实现的是限制一个数组具有特定枚举的每个字段。
我希望 allowedFruits
通过添加或删除 AllowedFruits
的字段显示类型错误。
有什么办法可以实现吗?
如果有任何我可以参考的文章或文档,请告诉我。
选项 1
我们可以通过创建一个包含 AllowedFruits
.
所有可能组合的类型来解决这个问题
type AllCombinations<T extends string | number> = [T] extends [never]
? []
: {
[K in T]: [K, ...AllCombinations<Exclude<T, K>>]
}[T]
type AllFruitCombinations = AllCombinations<AllowedFruits>
如果枚举中有很多元素,这可能会导致性能不佳,因为每个组合都需要先计算。
让我们看看这是否有效:
/* Error */
const t1: AllFruitCombinations = []
const t2: AllFruitCombinations = [AllowedFruits.Apple]
const t3: AllFruitCombinations = [AllowedFruits.Apple, AllowedFruits.Banana]
const t4: AllFruitCombinations = [AllowedFruits.Apple, AllowedFruits.Banana, AllowedFruits.Pear, AllowedFruits.Pear]
/* OK */
const t5: AllFruitCombinations = [AllowedFruits.Apple, AllowedFruits.Banana, AllowedFruits.Pear]
选项 2
也可以通过将 allowedFruits
传递给具有通用类型的函数来解决此问题。
我们可以创建一个通用的辅助类型 ExhaustiveFruits
来检查数组中是否存在所有枚举值。
type ExhaustiveFruits<
O extends AllowedFruits[],
T extends AllowedFruits[] = O,
P extends string = `${AllowedFruits}`
> = [P] extends [never]
? O
: T extends [`${infer L}`]
? [P] extends [L]
? O
: never
: T extends [`${infer L}`, ...infer R]
? R extends AllowedFruits[]
? ExhaustiveFruits<O, R, Exclude<P, L>>
: never
: never
ExhaustiveFruits
的逻辑非常简单:它是一个递归类型,我们从所有枚举值的并集开始作为 P
,AllowedFruits
的元组作为 T
.
对于 T
的每个元素,元素的 string
值由 '${infer L}'
推断。之后,此值从 P
与 Exclude<P, L>
的联合中删除。
每次迭代都会检查 P
是否为空 [P] extends [never]
或者 T
的最后一个元素是否是 P
的最后一个元素 [P] extends [L]
.如果是这种情况,可以返回原始元组 O
。如果 T
为空但 P
的联合中仍有 AllowedFruits
,则返回 never
。
类型可以在泛型函数中使用 createAllowedFruitsArray
像这样:
function createAllowedFruitsArray<
T extends AllowedFruits[]
>(arr: [...ExhaustiveFruits<T>]) : T {
return arr
}
检查一下是否有效:
createAllowedFruitsArray(
[] // Error
)
createAllowedFruitsArray(
[AllowedFruits.Apple] // Error
)
createAllowedFruitsArray(
[AllowedFruits.Apple, AllowedFruits.Banana] // Error
)
createAllowedFruitsArray(
[AllowedFruits.Apple, AllowedFruits.Banana, AllowedFruits.Pear] // OK
)
现在也可以多次使用相同的枚举值,只要全部使用即可。
createAllowedFruitsArray(
[AllowedFruits.Apple,
AllowedFruits.Banana,
AllowedFruits.Pear,
AllowedFruits.Pear] // Also ok, even though Pear is twice in the array
)
但稍微修改一下,我们也可以这样改:
type ExhaustiveFruits<
O extends AllowedFruits[],
T extends AllowedFruits[] = O,
P extends string | number = `${AllowedFruits}`
> = [P] extends [never]
? O["length"] extends 0
? O
: never
: T["length"] extends 1
? [P] extends [`${T[0]}`]
? O
: never
: T extends [any, ...infer R]
? R extends AllowedFruits[]
? [`${T[0]}`] extends [P]
? ExhaustiveFruits<O, R, Exclude<P, `${T[0]}`>>
: never
: never
: never
enum AllowedFruits {
Apple = 'APPLE',
Banana = 'BANANA',
Pear = 'PEAR'
}
const allowedFruits: AllowedFruits[] = [
AllowedFruits.Apple, AllowedFruits.Banana, AllowedFruits.Pear
]
我想要实现的是限制一个数组具有特定枚举的每个字段。
我希望 allowedFruits
通过添加或删除 AllowedFruits
的字段显示类型错误。
有什么办法可以实现吗?
如果有任何我可以参考的文章或文档,请告诉我。
选项 1
我们可以通过创建一个包含 AllowedFruits
.
type AllCombinations<T extends string | number> = [T] extends [never]
? []
: {
[K in T]: [K, ...AllCombinations<Exclude<T, K>>]
}[T]
type AllFruitCombinations = AllCombinations<AllowedFruits>
如果枚举中有很多元素,这可能会导致性能不佳,因为每个组合都需要先计算。
让我们看看这是否有效:
/* Error */
const t1: AllFruitCombinations = []
const t2: AllFruitCombinations = [AllowedFruits.Apple]
const t3: AllFruitCombinations = [AllowedFruits.Apple, AllowedFruits.Banana]
const t4: AllFruitCombinations = [AllowedFruits.Apple, AllowedFruits.Banana, AllowedFruits.Pear, AllowedFruits.Pear]
/* OK */
const t5: AllFruitCombinations = [AllowedFruits.Apple, AllowedFruits.Banana, AllowedFruits.Pear]
选项 2
也可以通过将 allowedFruits
传递给具有通用类型的函数来解决此问题。
我们可以创建一个通用的辅助类型 ExhaustiveFruits
来检查数组中是否存在所有枚举值。
type ExhaustiveFruits<
O extends AllowedFruits[],
T extends AllowedFruits[] = O,
P extends string = `${AllowedFruits}`
> = [P] extends [never]
? O
: T extends [`${infer L}`]
? [P] extends [L]
? O
: never
: T extends [`${infer L}`, ...infer R]
? R extends AllowedFruits[]
? ExhaustiveFruits<O, R, Exclude<P, L>>
: never
: never
ExhaustiveFruits
的逻辑非常简单:它是一个递归类型,我们从所有枚举值的并集开始作为 P
,AllowedFruits
的元组作为 T
.
对于 T
的每个元素,元素的 string
值由 '${infer L}'
推断。之后,此值从 P
与 Exclude<P, L>
的联合中删除。
每次迭代都会检查 P
是否为空 [P] extends [never]
或者 T
的最后一个元素是否是 P
的最后一个元素 [P] extends [L]
.如果是这种情况,可以返回原始元组 O
。如果 T
为空但 P
的联合中仍有 AllowedFruits
,则返回 never
。
类型可以在泛型函数中使用 createAllowedFruitsArray
像这样:
function createAllowedFruitsArray<
T extends AllowedFruits[]
>(arr: [...ExhaustiveFruits<T>]) : T {
return arr
}
检查一下是否有效:
createAllowedFruitsArray(
[] // Error
)
createAllowedFruitsArray(
[AllowedFruits.Apple] // Error
)
createAllowedFruitsArray(
[AllowedFruits.Apple, AllowedFruits.Banana] // Error
)
createAllowedFruitsArray(
[AllowedFruits.Apple, AllowedFruits.Banana, AllowedFruits.Pear] // OK
)
现在也可以多次使用相同的枚举值,只要全部使用即可。
createAllowedFruitsArray(
[AllowedFruits.Apple,
AllowedFruits.Banana,
AllowedFruits.Pear,
AllowedFruits.Pear] // Also ok, even though Pear is twice in the array
)
但稍微修改一下,我们也可以这样改:
type ExhaustiveFruits<
O extends AllowedFruits[],
T extends AllowedFruits[] = O,
P extends string | number = `${AllowedFruits}`
> = [P] extends [never]
? O["length"] extends 0
? O
: never
: T["length"] extends 1
? [P] extends [`${T[0]}`]
? O
: never
: T extends [any, ...infer R]
? R extends AllowedFruits[]
? [`${T[0]}`] extends [P]
? ExhaustiveFruits<O, R, Exclude<P, `${T[0]}`>>
: never
: never
: never