typescript 使用Map时了解对象中未定义的键

a0zr77ik  于 2022-11-26  发布在  TypeScript
关注(0)|答案(2)|浏览(245)

这是MWE:

interface Animals {cat:string, dog:string}

let a: Animals|undefined;

if(a){// if we got `a` from somewhere
 [1,2,3].map(v=>a.cat)
 // ----------> ~ Object is possibly 'undefined'
}

Playground
我不知道到底是什么错误,它似乎与Map输出的东西或未定义,但为什么a没有定义?

brccelvz

brccelvz1#

这是TypeScript的一般限制,如microsoft/TypeScript#9998中所述。控制流分析的影响(例如,在if (a)的真实性检查之后将aAnimals | undefined缩小到Animals)不会持续到跨函数边界的封闭值。
因此,在回调v => a.cat的主体内部,一直没有对a的控制流进行缩小;它被认为是Animals | undefined类型,因此在对其进行索引时会出现错误。
发生这种情况的原因是,编译器目前无法知道回调v => a.cat是否立即运行。这是我们所拥有的关于Array.prototype.map()的带外信息,但从类型系统的Angular 来看,[1,2,3].map和下面的foo定义之间没有任何有意义的区别:

function foo(cb: (x: number) => string) {
    setTimeout(() => cb(100), 1000);
}

a = { cat: "abc", dog: "def" };
if (a) {
    foo(v => a.cat) // error!
    // ----> ~ Object is possibly 'undefined'
}
a = undefined;
// later: Uncaught TypeError: a is undefined

foo()函数接受一个回调函数,并在 later 调用它。实际上,当它调用它时,a * 是 * undefined。因此,一般来说,编译器抱怨是正确的。你可以希望编译器注意到a是否被重新分配,如果没有,则抑制错误,但这对编译器来说是一个大量的额外工作。而microsoft/TypeScript#9998的要点是,他们还没有找到任何能够在编译器性能方面为自己带来回报的东西。
microsoft/TypeScript#11498中有一个特性请求,允许类型系统被告知map()立即运行其回调,以便任何控制流分析结果都可以在回调中持久化,这将允许map()foo()被区别对待......但目前它还不是语言的一部分。
这就是问题所在,编译器不知道回调函数会立即运行,也不会搜索源代码来检查a是否被重新分配给了undefined,所以它会在安全方面出错。
目前最好的解决方法是将缩小后的值赋给一个新的const变量,根据定义,该变量的类型已经缩小:

if (a) {
    const _a = a;
    [1, 2, 3].map(v => _a.cat) // okay
}

_a变量(如果存在)的类型始终为Animals,而不是Animals | undefined,因此回调类型检查成功。
Playground代码链接

aemubtdh

aemubtdh2#

发生这种情况的原因是TypeScript处理变量重新赋值的不确定性的方式。Here is the discussion on design choice。如果您将声明替换为const,则错误将在流节中清除,并在初始化时标记它。

相关问题