TypeScript 没有像这样表示详尽数组的内置功能。您可以尝试编写符合您标准的所有可能的tuples 中的union 类型,但这对于中等规模的联合来说不能很好地扩展,如果你想允许的话,甚至可能对于小情况都难以处理重复条目。
如果我真的想这样做,我会倾向于编写一个帮助函数,它会尝试从数组中推断字段名称,然后使用 conditional type,如果这些字段名称没有,则会导致编译器错误排气keyof T。这可能很脆弱,而且肯定很复杂,但这是一种可能的实现方式:
interface FieldNamed<K extends PropertyKey> {
name: K,
label: string
}
const exhaustiveFieldArray = <T extends object>() => <K extends keyof T>(
...fields: [FieldNamed<K>, ...FieldNamed<K>[]] &
(keyof T extends K ? unknown : FieldNamed<Exclude<keyof T, K>>[])
): Field<T>[] => fields;
const exhaustiveItemFieldArray = exhaustiveFieldArray<Item>();
函数exhaustiveFieldArray<T>() 采用手动指定的类型参数T 并返回一个新函数,该函数接受Field<T> 类型的可变参数数量,如果它不能确定您包含所有字段,则会抱怨名字。
在我们尝试解释它是如何工作的之前,让我们确保它工作:
const fields = exhaustiveItemFieldArray(
{ name: 'description', label: 'Description' },
{ name: 'location', label: 'location' }
); // okay
const badFields = exhaustiveItemFieldArray(
{ name: 'description', label: 'Description' },
{ name: 'locution', label: 'location' } // error!
//~~~~ <-- Type '"locution"' is not assignable to type 'keyof Item'
)
const badFields2 = exhaustiveItemFieldArray(
{ name: 'description', label: 'Description' } // error!
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type '"description"' is not assignable to type '"location"'.
)
const badFields3 = exhaustiveItemFieldArray(); // error!
// expected at least one argument
const badFields4 = exhaustiveItemFieldArray(
{ name: 'location', label: 'location' },
{ name: 'location', label: 'location' }
) // error!
我觉得这一切都还好。如果我们缺少字段,我们会得到错误。
这是它如何工作的草图。返回的函数是类型参数K extends keyof T中的generic。 fields rest 参数是两种类型的intersection。第一个用于推断传入的内容:
[FieldNamed<K>, ...FieldNamed<K>[]]
这意味着它是一个至少包含一个元素的数组(它是一个元素的元组,后跟some number of other elements),对于推断的K,每个元素都必须是FieldNamed<K> 类型。这将最终使 K 包含所有字段名称的并集。
第二种类型用于检查K 是否详尽无遗keyof T。我们已经知道K extends keyof T,但我们也想确保keyof T extends K:
& (keyof T extends K ? unknown : FieldNamed<Exclude<keyof T, K>>[])
此条件类型检查keyof T extends K。如果是真的,那么一切都很好,我们返回unknown。与unknown 相交是无操作(XYZ & unknown 等效于XYZ),因此不会阻止任何东西的编译。如果为假,那么我们有问题,我们返回FieldNamed<Exclude<keyof T, K>>[]。 The Exclude<T, U> utility type 从联合中移除元素;所以Exclude<keyof T, K> 为我们提供了我们遗漏的那些键。因此,我们将实际数组类型与我们错过的字段数组相交。这将导致编译器错误抱怨你错过了一些事情。这些错误可能不是最容易理解的,但至少有错误。
所以,万岁?它的工作原理,但我不知道它是否值得你。相反,您可以考虑将数据结构从数组(编译器难以检查)更改为键与 T 相同的对象(编译器易于检查)。但是这个答案已经很长了,所以我不打算进一步扩大范围来展示如何实现这样的事情。 ?
Playground link to code