我希望从联合或枚举类型为对象数组创建类型定义。如果联合中的所有项并非都作为对象数组中的键值存在于数组中,则类型定义失败。
TSPlayground
export type SelectValue<T = any> = {
value: T;
label: string;
};
type options = "email" | "sms";
//ideally make this type check fail because it does not have all of the values of the options union
const values: SelectValue<options>[] = [
{
value: "email",
label: "Email",
},
];
1条答案
按热度按时间xmakbtuz1#
TypeScript没有一个对应于"穷举数组"的内置类型,在"穷举数组"中,某个联合类型的每个成员都保证存在于数组中的某个位置。你也不能轻松地创建自己的特定类型,至少在联合中有很多成员或者你想允许重复的情况下是这样。
For a union with only a handful of members and if you want to prohibit duplicates, then you can could generate a union of all possible acceptable tuple types, like
[SelectValue<"email">, SelectValue<"sms">] | [SelectValue<"sms">, SelectValue<"email">]
for your example code (see here ). But that scales very badly in the number 𝑛 of members; the union of possible tuples containing one element for each member will itself have 𝑛! members (that's 𝑛 factorial ), which gets very big, very quickly. Unions in TypeScript can only hold about 100,000 elements at most, and the compiler slows down noticeably before that. This means if you have even eight elements in your union, you'll have a bad time.相反,在TypeScript中,你能得到的最接近的方法是编写一个泛型类型,作为数组类型的约束。也就是说,对于union
U
没有ExhaustiveArray<U>
类型;这里有一个泛型ExhaustiveArray<T, U>
类型,其中T extends ExhaustiveArray<T, U>
当且仅当T
是一个耗尽了U
所有成员的数组,并且你需要一个helper函数来阻止你自己写出T
,也就是说,你应该写const arr = exhaustiveArrayForMyUnion(...)
而不是const arr: ExhaustiveArray<MyUnion> = [...]
。我们来定义一下:
这里的
ExhaustiveArray<T, U>
是conditional type,它检查并集U
是否完全由数组T
的所有元素的并集来计算,如果是,它的值为T
(并且由于T extends T
,这将是成功的)。如果不是,则它评估为在结尾处比T
多一个元素的元组,包含所有缺失的元素(使用可变元组类型来追加元素,使用Exclude
实用程序类型来计算缺失的元素)。exhaustiveArray
值是一个curried helper函数,它接受一个union类型U
,并产生一个新函数,从传入的值中推断出T
,然后进行检查。如果我们可以写<T extends ExhaustiveArray<T, U>>(...t: [...T]) => t
,那就太好了,但是这是非法循环;或者如果<T extends readonly U[]>(...t: [...ExhaustiveArray<T, U>]) => t
工作,那就太好了,但是编译器不能那样从t
推断T
;上面的版本导致编译器首先从t
的值推断T
,然后将其转换为ExhaustiveArray<T, U>
。这种奇怪的方法的全部意义在于当你传入非穷举数组时得到一个"好"的错误消息。一个"坏"的错误消息是如果编译器只说"那个值不能赋值给never
",这很烦人,因为它不能帮助开发人员知道如何修复它。好的,我们来测试一下,首先我们来看看
exhaustiveSelectValueArray()
函数:其中类型参数是将
Options
并集O1 | O2 | O3 | ... | ON
转换为SelectValue<O1> | SelectValue<O2> | SelectValue<O3> | ... | SelectValue<ON>
的分布式Map类型。接下来是:
第一个调用成功是因为两个元素都传入了,而后两个调用失败是因为缺少一个参数。如果使用IntelliSense检查函数调用以查看缺少哪个参数,则两个调用都显示有一个类型为
SelectValue<"sms">
的预期参数未在最后传入。Playground代码链接