为什么有时需要在子函数范围内再次进行类型缩小.

我在下面的示例中编写了三个函数:

interface Obj {
  func?: Function;
}

const arr = [1, 2, 3];

function test1 (obj: Obj){
  if (obj.func !== void 0){
    arr.forEach(key => {
      obj.func(key);   // error, TS2722: Cannot invoke an object which is possibly 'undefined'.
    });
  }
}

function test2 (obj: Obj){
  if (obj.func !== void 0){
    arr.forEach(key => {
      if (obj.func !== void 0) {  // do narrowing again
        obj.func(key);   // ok
      }
    });
  }
}

function test3 (func?: Function){
  if (func !== void 0){
    arr.forEach(key => {
      func(key);   // ok, no second time narrowing
    });
  }
}

如您所见,在函数test1中,我们在函数体中进行类型缩小,但在forEach的回调函数中不再进行,而回调函数是另一个函数作用域,并且

然后在函数test3中,我将参数从一个对象更改为一个可选参数

有人能解释一下为什么在test1年后我需要再次缩小字体吗?

推荐答案

因为Typescript不知道回调函数是同步执行的.

这基本上是一样的:

let foo: string | null = 'abc'

if (foo) {
  setTimeout(() => foo.toUpperCase(), 1000) // Object is possibly 'null'.(2531)
}
foo = Math.random() > 0.5 ? 'def' : null

在这里,回调函数实际上是在以后执行的,任何数量的代码都已经运行了,从而改变了值.

因为这种缩小只在Typescript能够保证同步运行的范围内有效.而对于回调函数,它不能保证这一点.

我们仅有的关于forEach的类型信息是,它接受一个函数作为参数.实际调用该函数时,它根本不是类型的一部分.


在你的例子中:

function test1 (obj: Obj){
  if (obj.func !== void 0){
    arr.forEach(key => {
      obj.func(key);   // error, TS2722: Cannot invoke an object which is possibly 'undefined'.
    });
  }
}

在这里,理论上,其他一些文件中的一些代码,有对传递的obj的引用,可以将func prop设置为与判断prop的时间和实际运行回调函数的时间不同的时间.这确实是完全同步运行的,但同样,Typescript无法知道这一点.


function test3 (func?: Function){
  if (func !== void 0){
    arr.forEach(key => {
      func(key);   // ok, no second time narrowing
    });
  }
}

这是因为func的值不能从函数外变异.您正在传递一个函数或undefined,无论回调函数何时运行,该值都不会改变.


这也会起作用:

function test4 (obj: Obj){
  const func = obj.func
  if (func){
    arr.forEach(key => {
      func(key); // fine
    });
  }
}

在这里,我们将func属性本地存储在函数中.这样一来,如果其他地方发生了变化,这并不重要,因为我们有一个func的参考,从它发生变化之前.


最后,这只是函数的问题.简单的for循环Typescript可以保证同步:

function test5 (obj: Obj){
  if (obj.func){
    for (const key of arr) {
      obj.func(key); // fine
    }
  }
}

Playground

Typescript相关问答推荐

类型ElementTypes不可分配给类型ElementTypes.word'

为什么当类类型与方法参数类型不兼容时,该函数可以接受类类型

在OpenLayers 9.1中使用内置方法时,TypScript缩小联合类型

Typescript对每个属性具有约束的通用类型

如何在方法中定义TypeScript返回类型,以根据参数化类型推断变量的存在?

泛型函数类型验证

如何正确地对类型脚本泛型进行限制

迁移到Reaction路由V6时,获取模块Reaction-Router-Dom';没有导出的成员RouteComponentProps错误

Next.js错误:不变:页面未生成

使用打字Angular 中的通用数据创建状态管理

Angular 17子路由

如何创建一家Reduxstore ?

为什么在leetcode问题的测试用例中,即使我确实得到了比预期更好的解决方案,这段代码也失败了?

从泛型类型引用推断的文本类型

自动通用回调

打字错误TS2305:模块常量没有导出的成员

为什么数字是`{[key:string]:UNKNOWN;}`的有效密钥?

有没有一种更好的方法来存储内存中的数据,而不是在类型脚本中使用数组和基于索引的函数?

为什么TS条件返回要求不明确的强制转换而不是精确的强制转换?

如何避免TS2322;类型any不可分配给类型never;使用索引访问时