【问题标题】:Typescript - Incorrectly inferring 'never'打字稿 - 错误地推断“从不”
【发布时间】:2019-07-13 03:58:11
【问题描述】:

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

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

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

但是 typescript 将 a 的类型推断为 never。我会假设如果没有if,它会假设它是null | number,在这种情况下,我可能会收到一个错误,指出该属性在 null 上不存在,但为什么假设它永远不会仅基于初始分配价值。

我是不是做错了什么?

【问题讨论】:

  • 这意味着 typescript 可以确切地知道 forEach 在运行时做了什么,而它不知道。还要记住,可以修改 forEach 来做不同的事情,所以训练 typescript 来了解这一点是没有意义的。
  • TS 不会分析回调内部发生的情况。因为很难知道什么时候会调用回调(它们可能根本不会被调用)。

标签: typescript type-inference


【解决方案1】:

如果您绝对确定 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) { // <-- if undefined or 0
  a.toFixed(); // No problem here
}

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

【讨论】:

  • 好点。使用undefined 而不是null 是个好建议。
  • 如果你将变量初始化为未定义,它就不再起作用了。因此,不仅 TypeScript 不知道(这很好),而且可以肯定它永远不会被分配(这是不正确的)。让一个:数字|未定义=未定义; if(a) { // a 从不 }
  • 为什么要使用 undefined 来初始化变量?
【解决方案2】:

聚会迟到了,但这是我的 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、linter、..)否则会报告错误/警告。

例如当您使用具有默认 typescript 设置的 IntelliJ IDEA 时,会出现以下警告:

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

引用MDN: Global_Objects/undefined#description

未赋值的变量是未定义类型。

对于所有其他值(即 not 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 的变量的使用,但它确实将编译器变成了一种非常悲观的模式,当没有上下文推断类型的方法时,它会选择最窄的类型,而不是最宽的类型,

这意味着,详细地说:

  • 由于 typescript 编译器不够聪明,无法知道是否调用了 forEach 循环(因此分配了一个值),它采用悲观的方法并假设 x 仍然是 null
  • 因此,循环后x 的类型是null(而不是我们预期的number | null
  • 现在,最后一个 if 块检查 x !=== null 是否永远不会是这种情况(因为 typescript 假设在执行 if 语句时 x null。因此 @ 的类型if 语句中的 987654348@ 是 never
  • 所以一个“解决方案”是明确告诉 typescript 你确定,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();

【讨论】:

    猜你喜欢
    • 2021-06-08
    • 2020-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-29
    相关资源
    最近更新 更多