“对象可能是'undefined'。”在TypeScript中

时间:2019-08-07 00:34:06

标签: typescript

下面的代码在Object is possibly 'undefined'.处抛出obj.field1 += ',' + v;。 TypeScript表示obj可能是未定义的,但是此时obj不能是未定义的,因为在{field1: 'testtest'}返回未定义的情况下分配了map.get(key)

为什么会出现此错误?我该如何解决?

interface TestIF {
  field1: string;
}

export class MyClass {
  test(): void {
    const map1: Map<string, TestIF> = new Map();
    const map2: Map<string, string> = new Map();

    const key = 'mapkey';
    let obj = map1.get(key);
    if (obj == null) {
      obj = {field1: 'testtest'};
      map1.set(key, obj);
    }

    map2.forEach( v => {
      obj.field1 += ',' + v;  // Object is possibly 'undefined'.
    });
  }
}

3 个答案:

答案 0 :(得分:2)

发生错误是因为control flow analysis is difficult,尤其是当您要跟踪的信息无法在类型系统中表示时。

在一般情况下,编译器实际上无法弄清楚函数接受可变变量的回调时会发生什么。这些函数内部的控制流程是一个完全的谜。也许回调函数将立即被调用一次。也许永远不会调用该回调。也许回调将被调用一百万次。或者,也许它会在不久的将来被异步调用。由于一般情况是如此绝望,因此编译器甚至没有真正尝试过。它使用了一些启发式方法,这些方法在很多情况下都有效,并且在很多情况下也必然会失败。
您选择了其中一项失败。

这里使用的启发式方法是在回调内部,重置在较宽范围内发生的所有缩小。对于这样的代码,这可以做一些合理的事情:

// who knows when this actually calls its callback?
declare function mysteryCallbackCaller(cb: () => void): void;

let a: string | undefined = "hey";
mysteryCallbackCaller(() => a.charAt(0)); // error!  a may be undefined
a = undefined;

编译器不知道何时或是否调用() => a.charAt(0)。如果在调用mysteryCallbackCaller()时立即调用它,那么将定义a。但是,如果稍后再调用它,a可能是不确定的。由于编译器不能保证此处的安全性,因此会报告错误。


那么,在您的情况下,我们该如何解决这个问题?我可以想到两个主要解决方案。一种是只是告诉编译器这是错误的,并且您确定将定义obj。可以使用! non-null assertion operator

map2.forEach(v => {
  obj!.field1 += "," + v; // okay now
});

这没有编译时错误。此解决方案的警告是,确保定义obj的责任现在仅属于您,而不是编译器。如果您更改了前面的代码,而obj确实是未定义的,则类型断言仍将抑制该错误,并且在运行时会出现问题。


另一种解决方案是更改正在执行的操作,以便编译器 可以验证您的回调是否安全。最简单的方法是使用新变量:

// over here the compiler knows obj is defined
const constObj = obj; // type is inferred as TestIF
map2.forEach(v => {
  constObj.field1 += "," + v; // okay, constObj is TestIF, so this works
});

我在这里所做的全部工作是将obj分配给constObj。但是在进行此分配时,obj不能为undefined。因此constObj只是TestIF,而不是TestIF | undefined。而且由于constObj从未被重新分配,也无法undefined,因此其余代码可以正常工作。


好的,希望能有所帮助。祝好运!

Link to code

答案 1 :(得分:0)

地图的get方法定义为Map<string, TestIF>.get(key: string): TestIF | undefined,因此当您设置obj时,其类型为TestIF | undefined

obj块中(重置)if的类型时,它的作用域不同。当您在obj内阅读forEach时,它也在另一个范围内。 TypeScript编译器无法在更改的范围内建立正确的类型。

考虑此(有效)代码:

    const key = 'mapkey';
    let obj: TestIF; // Create variable with a Type
    if (map1.has(key)) { // We know (with certainty) that obj exists!
      obj = map1.get(key) as TestIF; // We use 'as' because we know it can't be Undefined
    } else {
      obj = { field1: 'testtest' };
      map1.set(key, obj);
    }

即使Map.get()总是返回V | undefined,当我们使用as时,我们还是强制TypeScript将其视为V。我谨慎使用as,但是在这种情况下,我们通过调用Map.has()来检查它的存在,从而知道它的存在。

我还要强调一点,(obj === undefined)(obj == null)好得多,后者只检查虚假性。 [more info]

答案 2 :(得分:0)

我现在也遇到了同样的问题,对我来说,解决方案是(适应您的情况):

if (!obj) {
   return ( ... );
}