打字稿-错误地推断“从不”

时间:2019-02-19 15:29:53

标签: typescript type-inference

这是一个基本用例:使用null初始化变量,然后在某些嵌套循环/函数中更改值:

let a: number | null = null;
[1].forEach(() => {
  a = 1;
});

if (a != null)
  a.toFixed(); // Error: Property 'toFixed' does not exist on type 'never'.

但是打字稿推断a的类型为never。我会假设没有if,它将假设它是null | number,在这种情况下,我可能会收到一条错误消息,指出该属性不存在于null上,但是为什么要假定它永远不会基于null呢?只是初始赋值。

我做错什么了吗?

2 个答案:

答案 0 :(得分:3)

如果您完全确定a那里有一个值,那么可以将!放在变量后面

let a: number | null = null;
[1].forEach(() => {
  a = 1;
});

if (a !== null)
  a!.toFixed(); //

我不会使用null而是使用undefined,所以不需要使用!

let a: number | undefined;
[1].forEach(() => {
  a = 1;
});

if (a) {
  a.toFixed(); // No problem here
}

还建议使用!==而不是!=

答案 1 :(得分:1)

聚会迟到了,但这是我的 2 美分。

评论已接受的答案

if (a) {
  a.toFixed(); // No problem here
}

请注意,当 a0 时,if 块将被调用。

  • 要解决此问题,请使用 if (a !== undefined)
  • 否则(当您真的不想处理 0 时,最好将 a 初始化为 0,如下所示:
    let a = 0; // typescript will infer the type number
    ...
    if (a) {
      // a is of type number and !== 0
    }

回复评论

<块引用>

为什么要使用 undefined 来初始化变量?

人们有时会这样做,因为某些工具(IDE、linters 等)会以其他方式报告错误/警告。

例如当您使用 IntelliJ IDEA 和默认的打字稿设置时,这是一个警告:
enter image description here

我建议停用这些检查,因为 javascript 中未初始化的变量始终具有值 undefined:即在某些其他语言(即 C)中,该变量可能具有一些随机的“垃圾”值。

引用自 MDN: Global_Objects/undefined#description

<块引用>

未赋值的变量是 undefined 类型。

对于所有其他值(即 undefined 的值),打字稿编译器将显示错误:
TS2454: Variable 'xxx' is used before being assigned.

回答原问题

let a: number | null = null;
[1].forEach(() => {
  a = 1;
});

if (a != null)
  a.toFixed(); // Error: Property 'toFixed' does not exist on type 'never'.

这仅在编译器选项 strictNullChecks 打开时发生。

这句话很好地描述了原因 (Quote Reference)

<块引用>

虽然 strictNullChecks 意味着它只是检查可能未定义或 null 的变量的使用情况,但它确实将编译器变成了一种非常悲观的模式,当没有上下文推断类型时,它将选择最窄的类型, 而不是最宽的类型,

这意味着:

  • 由于打字稿编译器不够聪明,无法知道是否调用了 forEach 循环(因此分配了一个值),因此它采用悲观方法并假设 x 仍然是 null
  • 因此,循环后 x 的类型是 null(不是我们预期的 number | null
  • 现在,最后的 if 块检查 if x !=== null 这永远不会是这种情况(因为 typescript 在执行 if 语句时假定 x is null。因此if 语句中 x 的类型是 never
  • 所以一个“解决方案”是明确告诉打字稿您确定,x 的值是通过使用 x!.toFixed() 定义的

杂项

strictNullChecks

strictNullChecks 关闭时,代码有效:TypeScript example: strictNullChecks=off
我强烈建议不要这样做。

for..of 循环

当您使用 for..of 循环而不是 forEach() 时,即使 strictNullChecks 打开:Playground

let a: number | null = null;
for (const i of [1]) {
  a = 1;
};
if (a != null)
  a.toFixed();

其他初始化值

您还可以考虑其他初始化值(而不是 undefinednull):Playground

let a = 0; // typescript will infer that a is of type number
[1].forEach(() => {
  a = 1;
});
if (a >= 0)
  a.toFixed();


let b = NaN; // typescript will infer that b is of type number
[1].forEach(() => {
  a = 1;
});
if (!isNaN(b))
  b.toFixed();