首页 > 解决方案 > 使用类型保护时的奇怪范围问题

问题描述

假设我们有这个打字稿代码:

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

打字稿游乐场

标签: typescripttypes

解决方案


一般的答案是在这种情况下你比编译器更聪明。TypeScript 使用启发式方法来分析代码的控制流,以尝试推断表达式的更窄类型。它在这方面做得很合理,但它并不完美,而且可能永远也不可能完美。

当您obj.bar在抛出 ifisA(obj)返回后立即访问时false,编译器会缩小obj到 include A,如您所料。不幸的是,当您创建一个闭包并将其传递给Array.prototype.forEach()时,编译器会扩大obj到其不包含的原始类型A。现在你我都知道它forEach()会立即调用它的回调函数,但 TypeScript 不会。据它所知,在obj调用回调之前将修改 的值。并且没有办法以不同的方式告诉编译器。所以它决定缩小范围不安全并放弃。

因此,解决方法:一个想法是制作objaconst而不是用 声明它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,即使它不是,真的

类似的解决方法,如果你不能做obja ,是在缩小发生后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
});

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


推荐阅读