给出一个使用诸如here之类的技术创建的强类型元组:
const tuple = <T extends string[]>(...args: T) => args;
const furniture = tuple('chair', 'table', 'lamp');
// typeof furniture[number] === 'chair' | 'table' | 'lamp'
我想在设计时断言它在另一种联合类型上是详尽无遗的:
type Furniture = 'chair' | 'table' | 'lamp' | 'ottoman'
如何创建一种类型,以确保furniture
包含Furniture
联合中的每个类型,并且仅包含它们?
目标是能够像这样在设计时创建一个数组,并在Furniture
更改时失败。理想的语法可能类似于:
const furniture = tuple<Furniture>('chair', 'table', 'lamp')
答案 0 :(得分:3)
有多种方法可以这样做,但对您来说可能有点混乱。这里的两个绊脚石是缺少partial type parameter inference和invalid types。这是我的解决方案:
type Furniture = 'chair' | 'table' | 'lamp' | 'ottoman';
const exhaustiveStringTuple = <T extends string>() =>
<L extends T[]>(
...x: L & ([T] extends [L[number]] ? L : [
Error, "You are missing ", Exclude<T, L[number]>])
) => x;
const missingFurniture = exhaustiveStringTuple<Furniture>()('chair', 'table', 'lamp');
// error, [string, string, string] is not assignable to parameter of type
// ["chair", "table", "lamp"] & [Error, "You are missing", "ottoman"]
const extraFurniture = exhaustiveStringTuple<Furniture>()(
'chair', 'table', 'lamp', 'ottoman', 'bidet');
// error, "bidet" is not assignable to a parameter of type 'Furniture'
const furniture = exhaustiveStringTuple<Furniture>()('chair', 'table', 'lamp', 'ottoman');
// okay
如您所见,exhaustiveStringTuple
是一个curried函数,其唯一目的是采用手动指定的类型参数T
,然后返回一个新函数,该函数采用类型为受T
约束,但受调用推断。 (如果我们有适当的部分类型参数推断,则可以消除这种混淆。)在您的情况下,T
将被指定为Furniture
。如果您只关心exhaustiveStringTuple<Furniture>()
,则可以改用它:
const furnitureTuple =
<L extends Furniture[]>(
...x: L & ([Furniture] extends [L[number]] ? L : [
Error, "You are missing ", Exclude<Furniture, L[number]>])
) => x;
const missingFurniture = furnitureTuple('chair', 'table', 'lamp');
// error, [string, string, string] is not assignable to parameter of type
// ["chair", "table", "lamp"] & [Error, "You are missing", "ottoman"]
const extraFurniture = furnitureTuple('chair', 'table', 'lamp', 'ottoman', 'bidet');
// error, "bidet" is not assignable to a parameter of type 'Furniture'
const furniture = furnitureTuple('chair', 'table', 'lamp', 'ottoman');
// okay
另一个问题是,省略必需的参数时出现的错误是。我只是做了一些垃圾,希望开发人员可以解释([Error, "You are missing ", "ottoman"]
,即使从类型上来讲这毫无意义。如果我们的类型无效,则可以自定义错误消息。
那是我能做的最好的。它的行为符合您的要求,但是正如我所说的,它可能比您想要的更混乱。希望能有所帮助;祝你好运!
答案 1 :(得分:0)
我还有其他主张
type RemoveFirstFromTuple<T extends any[]> =
T extends [] ? undefined :
(((...b: T) => void) extends (a: any, ...b: infer I) => void ? I : [])
const tuple = <T extends string[]>(...args: T) => args;
type FurnitureUnion = 'chair' | 'table' | 'lamp';
type FurnitureTuple = ['chair', 'table' , 'lamp'];
type Check<Union, Tuple extends Array<any>> = {
"error": never,
"next": Check<Union, RemoveFirstFromTuple<Tuple>>,
"exit": true,
}[Tuple extends [] ? "exit" : Tuple[0] extends Union ? "next" : "error"];
type R = Check<FurnitureUnion, FurnitureTuple>; // true
type R1 = Check<'chair' | 'lamp' | 'table', FurnitureTuple>; // true
type R2 = Check<'chair' | 'lamp' | 'table', ['chair', 'table' , 'lamp', 'error']>; // nerver
从元组中删除将获取元组并返回不包含第一个元素的元组(稍后将需要)
检查将在元组上迭代。当Tuple [0]不扩展Union时,每个步骤都可以返回never
,在输入tuple为空时退出,并在Tuple [0]扩展Union时返回下一步。在下一步中,我们递归调用Check,但是首先我们使用先前的util