TypeScript空数组在不同编译配置下的类型推断问题咨询
TypeScript空数组类型推断反直觉现象说明
复现代码
let a = []; a.push(0);
不同tsconfig配置下的实际表现
- 配置1:
noImplicitAny: true、strictNullChecks: true:代码正常编译,a被推断为any[]类型 - 配置2:
noImplicitAny: false、strictNullChecks: false:代码正常编译,a被推断为any[]类型 - 配置3:
noImplicitAny: false、strictNullChecks: true:第二行代码抛出编译错误:Argument of type 'number' is not assignable to parameter of type 'never'.,此时a被推断为never[]类型
规则层面的核心原因
这个表现和多数人直觉相反,本质是对两个编译配置的作用边界存在认知偏差,实际是两条推断规则叠加的正常结果:
strictNullChecks是决定空数组初始推断结果的核心开关
当这个配置关闭时,TypeScript的类型检查处于宽松模式,没有初始元素、没有显式类型注解、也没有上下文类型约束的空数组,会直接被推断为any[],这个推断结果不受noImplicitAny配置影响,这就是配置2能正常编译的原因。
当这个配置开启时,上述场景下的空数组第一阶段会被推断为never[]——因为编译器没有任何信息能确定数组里应该存什么类型,never作为所有类型的子类型,是最保守的初始推断结果。noImplicitAny附带的兜底拓宽逻辑只在配置开启时生效
很多人以为noImplicitAny开了才会禁止any、关了就会优先出any,实际正好相反:noImplicitAny的核心作用是给推断出隐式any的场景抛错,为了避免开了这个配置之后,无类型空数组被钉死为never[]导致所有push操作都报错,编译器专门加了一层兜底逻辑:当noImplicitAny开启时,初始推断为never[]的空数组会自动拓宽为any[],而且数组字面量的这个拓宽操作属于豁免场景,不会触发隐式any的报错,这就是配置1下a是any[]、代码能正常编译的原因。
当noImplicitAny关闭时,编译器默认用户不需要这类为了规避隐式any报错做的特殊兜底,就会严格保留空数组初始推断的never[]类型,不会自动拓宽为any[],此时往数组里push数字自然会触发类型不匹配的错误,也就是配置3的表现。
效果参考
- 前两种正常编译场景效果:

- 第三种报错场景效果:

内容的提问来源于stack exchange,提问作者gaps
相关产品推荐
相关产品推荐

