使用 typeguards 时的奇怪范围问题 [英] Strange scoping issue when using typeguards
问题描述
假设我们有这个打字稿代码:
Say we have this typescript code:
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
中无效?
Why is obj.bar
not valid while inside the forEach
?
推荐答案
一般的答案是,在这种情况下,您比编译器更聪明.TypeScript 使用启发式来分析代码的控制流以尝试为表达式推断更窄的类型.它在这方面做得很合理,但是它并不完美,而且可能永远不会是.
The general answer is that you are cleverer than the compiler in this case. TypeScript uses heuristics to analyze the control flow of the code to try to infer narrower types for expressions. It does a reasonable job of this, but it isn't perfect and probably can never be.
当你在抛出 if isA(obj)
返回 false
后立即访问 obj.bar
时,编译器缩小 obj
包含 A
,如您所料.不幸的是,当您创建一个闭包并将其传递给 Array.prototype.forEach()
时,编译器会将 obj
扩展回不包含 A 的原始类型代码>.现在你我都知道
forEach()
会立即调用它的回调函数,但 TypeScript 不会.据它所知,obj
的值将在调用回调之前被修改.并且没有办法以不同的方式告诉编译器.所以它决定缩小不安全并放弃.
When you access obj.bar
immediately after throwing if isA(obj)
returns false
, the compiler narrows obj
to include A
, as you expect. Unfortunately, when you create a closure and pass it to Array.prototype.forEach()
, the compiler widens obj
back to its original type which does not include A
. Now you and I know that forEach()
will immediately invoke its callback function, but TypeScript doesn't. For all it knows, the value of obj
will be modified before the callback is invoked. And there's no way to tell the compiler differently. So it decides that narrowing isn't safe and gives up.
所以,变通方法:一个想法是让 obj
成为一个 const
而不是用 let
声明它:
So, workarounds: one idea is to make obj
a const
instead of declaring it with 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
的回调之前添加或删除 obj.bar
的事实,但是 TypeScript 使用的启发式类似于const
比 let
更可能是不可变的",即使 不是,真的.
This doesn't actually change the fact that obj.bar
can be added or removed before a callback that closes over obj
is called, but the heuristic that TypeScript uses is something like "const
is more likely to be immutable than let
", even though it's not, really.
一个类似的解决方法,如果你不能让 obj
成为一个 const
,那么在缩小发生后分配一个新的 const
变量,并在回调中使用它:
A similar workaround, if you can't make obj
a const
, is to assign a new const
variable after the narrowing takes place, and use that in the callback instead:
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
中间人:
Of course by that token you might as well skip the obj
middleman entirely:
let obj = { foo: 'hello' };
if (!isA(obj)) throw 'wont ever throw'
const bar = obj.bar;
Array(5).fill(0).forEach((_, i) => {
bar // This is okay
});
这取决于你.希望有帮助.祝你好运!
It's up to you. Hope that helps. Good luck!
这篇关于使用 typeguards 时的奇怪范围问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!