这是在标准库的ReadonlyArray 接口(以及一般的ReadonlyArray 接口)上定义map 方法的一个缺点:无论输入类型是否为元组与否:
interface ReadonlyArray<T> {
map<U>(callbackfn: (value: T, index: number, array: readonly T[]) => U, thisArg?: any): U[]
}
由于ReadonlyArray 中的值类型由泛型类型参数T 确定,因此无法确定索引和值之间的确切关系,这对于能够返回元组至关重要。
使用带有as 的类型断言的一个本质上不安全但简单的方法是already provided(它本身并没有错,因为您的情况正是您比编译器知道的更多的情况)。
您可能知道的另一个更安全但繁琐的解决方案是将您的变量定义为number | undefined(如果您定义了一个帮助器MaybeNum 或类似的东西,那么繁琐一点),然后使用类型保护来让编译器放心.
最后,您可以利用声明合并技术并为map 方法提供您自己的重载,该方法将使用this 推断来获取元组类型。使用它,您可以返回一个映射类型,其中值被推断的U 替换:
interface ReadonlyArray<T> {
map<U>(callbackfn: (value: T, index: number, array: readonly T[]) => U, thisArg?: any): { [ P in keyof this ] : U };
}
您的 double 函数现在只需要调整即可告诉编译器 x 将成为 readonly 数组:
const dbl = <T extends readonly number[]>(x: T) => x.map(a => a * 2);
结果正是您想要的(显然,当您传递数组文字时需要as const,以便将其视为元组,但这是一个很小的代价),更重要的是,ReadonlyArray方法还是可以正常使用的:
function foo2() {
let a: number = 5;
let b: number = 6;
[a, b] = dbl([b, a] as const); //OK
}
dbl([5,6] as const).forEach((a) => console.log(a)); //OK, a is number here
Playground