使用 typeguards 时的奇怪范围问题 [英] Strange scoping issue when using typeguards

查看:25
本文介绍了使用 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.barforEach 中无效?

Why is obj.bar not valid while inside the forEach?

Typescript Playground

推荐答案

一般的答案是,在这种情况下,您比编译器更聪明.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 使用的启发式类似于constlet 更可能是不可变的",即使 不是,真的.

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆