TypeScript剩余参数场景下为何无法正确执行类型收窄?
问题原因
这个类型错误来自TypeScript对数组、元组类型的控制流分析局限性:
- 你定义的剩余参数类型
[Coords] | number[]是两个数组类型的联合,TS不会因为你判断了x[0]类型为number,就自动把整个x收窄为number[]类型。 - TS对数组索引的类型收窄是局部的:
typeof x[0] === "number"仅能让TS确定x[0]是number,不会推导x[1]、x[2]等其他索引位的类型,也不会排除x为[Coords]元组的可能。 - 逻辑上
[Coords]是长度为1的元组,x为该类型时x[1]应为undefined,但TS处理「固定长度元组 | 普通数组」的联合类型时,不会对元组越界访问做静态校验,所有索引位的元素类型都会被计算为两个数组成员的元素类型联合,也就是Coords | number,因此x[1]等位置会被判定为可能传入Coords数组类型,无法直接赋值给要求number类型的矩阵元素位,触发类型错误。
修复方案
你可以选择以下任意一种方式解决问题:
方案1:分支内类型断言(实现最简单)
在判断x[0]为number的分支内,直接将x断言为number[]类型,跳过TS的不完整推导即可:
if(typeof(x[0]) == "number") { const nums = x as number[]; return [ // Transposed 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 , nums[0] , nums[1]??0 , nums[2]??0 , nums[3]??1 , ] }
方案2:函数重载(类型定义最严谨)
为函数添加重载签名,明确两种入参模式对应的类型,TS可以在实现中正确识别分支类型:
// 重载:传入单个Coords类型参数 export function translate(coords: Coords): Matrix; // 重载:传入多个number类型参数 export function translate(...nums: number[]): Matrix; // 实现签名 export function translate(...x:[Coords]|number[]):Matrix{ if(typeof(x[0]) == "number") { const nums = x as number[]; return [ 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 , nums[0] , nums[1]??0 , nums[2]??0 , nums[3]??1 , ] } else { const X = normalize(x[0]) return [ 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 , X[0] , X[1] , X[2] , X[3] , ] } }
方案3:自定义类型守卫(运行时安全性最高)
编写类型守卫函数校验数组所有元素都是number,在判断通过时将x的类型收窄为number[],还可以避免混传number和Coords导致的隐藏运行时问题:
function isNumberArray(arr: unknown[]): arr is number[] { return arr.every(item => typeof item === "number"); } export function translate(...x:[Coords]|number[]):Matrix{ if(isNumberArray(x)) { return [ 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 , x[0] , x[1]??0 , x[2]??0 , x[3]??1 , ] } else { const X = normalize(x[0]) return [ 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 , X[0] , X[1] , X[2] , X[3] , ] } }
内容的提问来源于stack exchange,提问作者hl037_
相关产品推荐
相关产品推荐

