说我们有这个打字稿代码:
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.bar
在forEach
内时无效?
答案 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
由您决定。希望能有所帮助。祝你好运!