正如你所怀疑的那样,TypeScript不会根据对其
length
属性的检查自动缩小数组类型。这在
microsoft/TypeScript#38000中已经被建议过,但被标记为“太复杂”。看起来在此之前,
microsoft/TypeScript#28837也曾被提出过,该问题仍然处于开放状态,并标记为“等待更多反馈”。也许你可以去那个问题并留下反馈,说明为什么这样做对你有帮助,以及当前的解决方案为什么不足,但我不知道它会产生多大的影响。无论如何,我怀疑TS团队现在不会接受拉取请求来实现这样的功能。
在没有任何自动缩小的情况下,你可以编写一个
用户定义的类型保护函数,以达到你想要的效果。以下是一个可能的实现:
type Indices<L extends number, T extends number[] = []> =
T['length'] extends L ? T[number] : Indices<L, [T['length'], ...T]>;
type LengthAtLeast<T extends readonly any[], L extends number> =
Pick<Required<T>, Indices<L>>
function hasLengthAtLeast<T extends readonly any[], L extends number>(
arr: T, len: L
): arr is T & LengthAtLeast<T, L> {
return arr.length >= len;
}
Indices<L>
类型旨在接受一个单独的、相对较小的、非负的整数
literal typeL
,并返回长度为
L
的数组的数字索引的
union。换句话说,
Indices<L>
应该是小于
L
的非负整数的联合。例如:
type ZeroToNine = Indices<10>
// type ZeroToNine = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
这种类型的工作原理是利用
递归条件类型和
可变元组类型从
0
到
L
进行遍历。递归类型在处理正常情况时效果很好,但有一个大问题,如果传入的
L
过大、为分数、为负数、为数字或联合,则会出现异常或错误。这是这种方法的一个重要限制。
接下来,
LengthAtLeast<T, L>
需要一个数组类型
T
和一个长度
L
,并返回一个对象,该对象已知在长度至少为
L
的数组的所有索引处具有属性。就像这样:
type Test = LengthAtLeast<["a"?, "b"?, "c"?, "d"?, "e"?], 3>
type Test2 = LengthAtLeast<string[], 2>
最后,
hasLengthAtLeast(arr, len)
是类型守卫函数。如果它返回
true
,那么
arr
的类型将从
T
缩小为
T & LengthAtLeast<T, L>
。让我们看看它的实际应用:
const myFunc = (myArray: string[]) => {
if (hasLengthAtLeast(myArray, 6)) {
myArray[0].toUpperCase();
myArray[5].toUpperCase();
myArray[6].toUpperCase();
}
}
看起来不错。编译器允许您将myArray[0]
和myArray[5]
视为已定义,但myArray[6]
仍然可能未定义。
无论如何,如果您决定使用类型守卫,您可能需要在复杂性和使用频率之间取得平衡。 如果您只检查长度的几个位置,那么仅使用
非null
断言操作符(例如
myArray[0]!.toUpperCase()
)可能是值得的,不必担心编译器为您验证类型安全性。
或者,如果您无法控制
len
的值,则可能不希望使用脆弱的递归条件类型,而是构建更健壮但不太灵活的东西(例如,仅适用于特定
len
值的重载类型守卫,如
在microsoft/TypeScript#38000的评论中)。
这一切都归结于您的使用情况。
代码的Playground链接
hasLengthAtLeast(arr, 6)
代替arr.length >= 6
。如果你想了解更多信息,我可以在有机会时为你撰写答案。否则,我的回答就是“不行”。请告诉我你的想法。 - jcalz