typescript - 使用类型保护时的奇怪范围问题
问题描述
假设我们有这个打字稿代码:
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
?
解决方案
一般的答案是在这种情况下你比编译器更聪明。TypeScript 使用启发式方法来分析代码的控制流,以尝试推断表达式的更窄类型。它在这方面做得很合理,但它并不完美,而且可能永远也不可能完美。
当您obj.bar
在抛出 ifisA(obj)
返回后立即访问时false
,编译器会缩小obj
到 include A
,如您所料。不幸的是,当您创建一个闭包并将其传递给Array.prototype.forEach()
时,编译器会扩大obj
到其不包含的原始类型A
。现在你我都知道它forEach()
会立即调用它的回调函数,但 TypeScript 不会。据它所知,在obj
调用回调之前将修改 的值。并且没有办法以不同的方式告诉编译器。所以它决定缩小范围不安全并放弃。
因此,解决方法:一个想法是制作obj
aconst
而不是用 声明它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 使用的启发式方法是“const
比”更可能是不可变的let
,即使它不是,真的。
类似的解决方法,如果你不能做obj
a ,是在缩小发生后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
完全跳过中间人:
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
});
由你决定。希望有帮助。祝你好运!
推荐阅读
- java - 在被标记化后,如何将具有两种数据类型的构造函数中的数据存储到数组中?
- tinymce - TINYMCE - 如何关闭所有自动替换?
- javascript - 根据文本长度调整下拉菜单的宽度
- python - Django rest 框架序列化程序创建方法声称参数数量错误
- nodes - 为什么某些网桥节点会出现连接问题?
- laravel - Laravel setUpBeforeClass上的PHPUnit找不到工厂类
- c# - 使用 mongodb c# driver 插入一个文档和子文档数组
- python - Python pandas:获取会话开始和结束时间以计算会话长度
- java - 响应数据忽略属性
- python - Flask-sqlalchemy 多对多关系数据