使用Typeguard时出现奇怪的作用域问题

时间:2018-10-10 15:23:53

标签: typescript types

说我们有这个打字稿代码:

interface A { 
  bar: string;
}
const isA = <T>(obj: T): obj is T & A => {
  obj['bar'] = 'world';
  return true;
}

let obj = { foo: 'hello' };
if (!isA(obj)) throw 'wont ever throw'

obj.foo // This is ok
obj.bar // This is ok

Array(5).fill(0).forEach((_, i) => {
  obj.foo // This is ok
  obj.bar // This is not ok
});

为什么obj.barforEach内时无效?

Typescript Playground

1 个答案:

答案 0 :(得分:2)

一般的回答是,在这种情况下,您比编译器更聪明。 TypeScript使用启发式方法对analyze the control flow的代码进行尝试,以推断出较窄的表达式类型。它可以做到这一点,但是it isn't perfect可能永远做不到。

如果在obj.bar返回isA(obj)的情况下抛出后立即访问false,则编译器将obj的范围缩小到A,这是您所期望的。不幸的是,当您创建一个闭包并将其传递给Array.prototype.forEach()时,编译器会将obj扩宽为不包含A的原始类型。现在您和我知道forEach()将立即调用其回调函数,但TypeScript不会。就其所知,在调用回调之前将修改obj的值。而且没有办法tell进行不同的编译。因此,它认为缩小范围并不安全并放弃了。

因此,解决方法:一个想法是将obj设为const,而不用let声明它:

const obj = { foo: 'hello' };
if (!isA(obj)) throw 'wont ever throw'
Array(5).fill(0).forEach((_, i) => {
  obj.bar // This is okay now
});

这实际上并没有改变以下事实:可以在调用obj.bar结束的回调之前添加或删除obj,但是TypeScript使用的启发式方法类似于“ {{1}与const“相比,} it's not, really更可能是不可变的。

如果无法将let设为obj,则类似的解决方法是在缩小范围后分配新的const变量,并在回调中使用该变量:

const

当然,您也可以完全跳过let obj = { foo: 'hello' }; if (!isA(obj)) throw 'wont ever throw' const myObj = obj; Array(5).fill(0).forEach((_, i) => { myObj.bar // This is okay }); 中间人:

obj

由您决定。希望能有所帮助。祝你好运!