像[1, 2]
这样的Tuple types是array types的子类型,因此具有与数组的元素类型相对应的数字index signatures.也就是说,the Array<T>
declaration includes
interface Array<T> {
[n: number]: T
}
请注意,这是T
而不是T | undefined
,即使当您从数组的任意数字索引读取时,也很可能得到undefined
的结果.当你使用像Array<T>[number]
这样的indexed access type时,你得到的是属性类型T
,而不是T | undefined
.
这一行为早于null/undefined checking的存在.一旦你可以预料到| undefined
会出现在可能未定义的类型上,人们就开始要求索引签名做这件事,microsoft/TypeScript#13778获得了很多社区的支持.但打字团队has always been skeptical认为这实际上是可取的.强迫人们判断undefined
的索引签名将导致许多惯用的JavaScript无法编译.尽管如此,它仍然很受欢迎,所以最终引入了a --noUncheckedIndexedAccess
compiler flag来实现它,这样当您从具有索引签名的类型读取时,您必须判断undefined
.这是在microsoft/TypeScript#39560中实施的,其中解释说:
Design Point: What's the type of T[number]
/ T[string]
?
在实现此功能时,会给出以下类型的问题:
type Q = (boolean[])[number];
Q
应该是boolean
(合法写入数组的类型)还是boolean | undefined
(如果从数组中读取就会得到的类型)?
判断此代码:
// N.B. not a good function signature; don't do this in real code
function zzz<T extends unknown[]>(arr: T, i: number): T[number] {
return arr[i];
}
感觉在这种模式下,判断员的工作就是说"zzz
进行可能超出范围的读取,需要澄清".然后,您可以编写以下任一代码:
// Allows out-of-bounds access to silently occur
function zzz1<T extends unknown[]>(arr: T, i: number): T[number] | undefined {
return arr[i];
}
// - or -
// Bounds checks for you
function zzz2<T extends unknown[]>(arr: T, i: number): T[number] {
// TODO: Validate that 'i' is in bounds
return arr[i]!; // ! - safety not guaranteed but we tried
}
这有very的积极副作用,它意味着声明文件中的T[number]
不会根据是否设置了标志而改变含义,并保持
type A = { [n: number]: B }[number]
// A === B
简而言之,他们决定让T[number]
成为索引的较窄的write类型,而不是较宽的read类型,这样返回边界之外的实际值就是类型T[number] | undefined
,并且声明文件和以前识别的类型标识不会改变. 无论人们是否同意这些理由,这似乎是这个问题的标准答案.