类型系统并不是特别适合保证给定元组与给定联合类型的成员具有完全相同的值和元素数量。有各种各样的方法可以尝试实现这一点。
我将介绍一种方法:创建一个辅助函数,它采用联合类型(如 Fruit)并返回一个数组builder,提示开发人员添加联合的每个单独成员,并且在每一步中,它都会从可能的下一个元素列表中删除先前添加的成员,最后只允许您构建数组。这仅适用于 literal types 的联合(或其他每个只有一个可能值的单元类型)。
在这里(如果它太难看,你可以把它放在图书馆的某个地方)
// TS4.0+
type Push<T extends readonly any[], V> = [...T, V];
type ReadonlyTuple<T> = Extract<Readonly<T>, readonly any[]>;
interface TupleBuilderAddable<U, T extends readonly any[]> {
add<V extends U>(v: V): [U] extends [V] ?
TupleBuilderBuildable<ReadonlyTuple<Push<T, V>>> :
TupleBuilderAddable<Exclude<U, V>, ReadonlyTuple<Push<T, V>>>;
}
interface TupleBuilderBuildable<T extends readonly any[]> {
build(): T;
}
function tupleBuilder<U>():
[U] extends [never] ?
TupleBuilderBuildable<[]> :
TupleBuilderAddable<U, []> {
const tuple: any[] = [];
const ret = {
add(v: any) {
tuple.push(v);
return ret;
},
build() {
return tuple;
}
}
return ret as any;
}
“简要”解释:
-
Push<T, V> 接受一个元组类型T 和一些类型V 并在T 的末尾附加V 创建一个新元组。
-
TupleBuilderBuildable<T> 采用元组类型 T 并表示具有 build() 方法的对象,该方法返回 T 类型的值。
-
TupleBuilderAddable<U, T> 采用联合类型 U 和元组类型 T 并表示具有 add() 方法的对象。这个add() 方法接受一个扩展U 的V 类型的值。从概念上讲,它将从U 中删除V 以获得新的联合类型U',然后将V 推到T 元组上以获得新的元组类型T'。如果U' 为空,则add() 返回TupleBuilderBuildable<T'>。否则返回TupleBuilderAddable<U', T'>。这是类型系统中的重要一步:每次调用 add() 时,都会将一个元素从联合转移到元组。
在运行时,您只有一个函数,该函数返回一个对象,该函数同时具有 add() 和 build() 方法,分别推送到数组并返回数组。但在编译时,只有 add() 或 build() 会暴露出来,具体取决于联合类型中是否还有未使用的内容。
足够的解释;以下是您的使用方法:
type Fruit = 'apple' | 'peach';
const t = tupleBuilder<Fruit>().add("apple").add("peach").build();
// const t: readonly ["apple", "peach"]
IntelliSense 仅允许您执行某些操作:
const builder0 = tupleBuilder<Fruit>();
// const builder0: TupleBuilderAddable<Fruit, []>
// builder0 only has an add() method
builder0.add("cherry"); // error! "cherry" is not assignable to Fruit.
const builder1 = builder0.add("peach"); // okay
// const builder1: TupleBuilderAddable<"apple", readonly ["peach"]>
// builder1 only has an add method
builder1.add("peach"); // error! "peach" is not assignable to "apple".
const builder2 = builder1.add("apple"); // okay
// const builder2: TupleBuilderBuildable<readonly ["peach", "apple"]>
// builder2 only has a build method
const u = builder2.build();
// const u: readonly ["peach", "apple"]
它会强制您按某种顺序使用add()"apple" 和"cherry",然后是build()。是的,它很冗长,但编译器会全程为您提供提示。在添加完所有元素之前不能调用build(),有之后也不能调用add(),也不能添加错误或重复的元素。当你全部完成后,你就有了一个元组类型。
不确定这是否符合您的用例,但我只是想展示一种可能性。祝你好运!
Link to code