这种方法需要额外的工作,因为它从实际元组派生所需的元组类型。
“嗯,”你在想,“如果我必须提供我需要的确切类型,那有什么用呢? >?”
我同意。但是,它对于相关用例仍然有用:确保元组包含联合类型的所有元素(例如,用于单元测试)。在这种情况下,您需要声明数组无论如何,因为 Typescript 无法从类型生成运行时值。因此,“额外”的工作变得毫无意义。
注意:作为先决条件,我依赖于 TypesEqual 运算符 as discussed here。为了完整起见,这是我正在使用的版本,但还有其他选项:
type FunctionComparisonEqualsWrapped<T> =
T extends (T extends {} ? infer R & {} : infer R)
? { [P in keyof R]: R[P] }
: never;
type FunctionComparisonEquals<A, B> =
(<T>() => T extends FunctionComparisonEqualsWrapped<A> ? 1 : 2) extends
<T>() => T extends FunctionComparisonEqualsWrapped<B> ? 1 : 2
? true
: false;
type IsAny<T> = FunctionComparisonEquals<T, any>;
type InvariantComparisonEqualsWrapped<T> =
{ value: T; setValue: (value: T) => never };
type InvariantComparisonEquals<Expected, Actual> =
InvariantComparisonEqualsWrapped<Expected> extends
InvariantComparisonEqualsWrapped<Actual>
? IsAny<Expected | Actual> extends true
? IsAny<Expected> | IsAny<Actual> extends true
? true
: false
: true
: false;
export type TypesEqual<Expected, Actual> =
InvariantComparisonEquals<Expected, Actual> extends true
? FunctionComparisonEquals<Expected, Actual>
: false;
export type TypesNotEqual<Expected, Actual> =
TypesEqual<Expected, Actual> extends true ? false : true;
下面是实际的解决方案和示例用法:
export function rangeOf<U>() {
return function <T extends U[]>(...values: T) {
type Result = true extends TypesEqual<U, typeof values[number]> ? T : never;
return values as Result;
};
}
type Letters = 'a' | 'b' | 'c';
const letters = rangeOf<Letters>()(['a', 'b', 'c']);
type LettersTuple = typeof letters;
一些注意事项:rangeOf 不关心元组的顺序,它允许重复条目(例如,['b', 'a', 'c', 'a'] 将满足 rangeOf<Letters>())。对于单元测试,这些问题可能不值得关心。但是,如果您确实关心,您可以在返回之前对 values 进行排序和重复数据删除。这会在初始化期间以少量运行时性能换取“更干净”的表示。