function foo(item: string | null) {
  if (!item) return null
  return () => {
    function regular() {
      return item
    }
    const arrow = () => item
    console.log(regular().toLowerCase()) // Error: Object is possibly 'null'.
    console.log(arrow().toLowerCase()) // Pass
  }
}

Playground Here

在函数foo中,我编写了两个嵌套函数.一个是正则函数,另一个是箭头函数.虽然我认为返回的两个值的类型都应该缩小到string,但事实是regular()得到了string | null类型.

起初,我认为这可能是因为函数参数item是可变的.(在本issue中提到),所以我手动将其转换为const,但它仍然存在相同的问题:

function foo(item: string | null) {
  const itemCopy = item // Make sure TypeScript know it is immutable
  if (!itemCopy) return null
  return () => {
    function regular() {
      return itemCopy
    }
    const arrow = () => itemCopy
    console.log(regular().toLowerCase()) // Error: Object is possibly 'null'.
    console.log(arrow().toLowerCase()) // Pass
  }
}

Playground Here

还提到,它似乎与箭头功能无关.将箭头函数更改为正则函数表达式也将显示相同的行为.

function foo(item: string | null) {
  if (!item) return null
  return () => {
    function regular() {
      return item
    }
    const exp = function () { // Now the only difference is one is function expression while the other is not.
      return item
    }
    console.log(regular().toLowerCase()) // Error: Object is possibly 'null'.
    console.log(exp().toLowerCase()) // Pass
  }
}

Playground Here

为什么会这样呢?这种行为是有意为之的吗?

推荐答案

根据microsoft/TypeScript#32300的说法,这似乎起到了预期的作用.从根本上说,function declarations/statements的行为与function expressions的不同之处在于,函数声明是hoisted.提升的函数声明可以在其定义出现在代码中之前调用,而非提升的函数表达式不能:

function foo(item: string | null) {
  if (!item) return null
  return () => {
    regular(); // okay
    exp(); // error
    function regular() {
      return item
    }
    const exp = function () {
      return item
    }
  }
}

这意味着control flow analysis有不同的含义.看起来,对于提升函数类型脚本,它只是放弃,悲观地假设任何闭合变量都没有被缩小,而对于函数表达式,它乐观地假设从早期的持久化开始的任何缩小.目前还不清楚这两种行为是否"正确",但它肯定是按照设计的方式运行的.

microsoft/TypeScript#9998中深入讨论了在封闭变量面前缩小的一般问题.TypeScript做了很多简化的假设,这些假设在很多情况下都能很好地工作,但在边缘情况下会出现不幸的假阴性和假阳性. 通常,当人们发现一个不一致的地方时,它并不是一个bug,而是一个有缺点的设计 Select .

Playground link to code

Typescript相关问答推荐

有没有可能使用redux工具包的中间件同时监听状态的变化和操作

忽略和K的定义

如何推断哪个特定密钥与泛型匹配?

TypeScript:在作为参数传递给另一个函数的记录中对函数的参数实现类型约束

无法正确推断嵌套泛型?

来自类型脚本中可选对象的关联联合类型

如果数组使用Reaction-Hook-Form更改,则重新呈现表单

如何将定义模型(在Vue 3.4中)用于输入以外的其他用途

如何在Typescript 中组合unions ?

在列表的指令中使用intersectionObservable隐藏按钮

使用动态输出类型和泛型可重用接口提取对象属性

保护函数调用,以便深度嵌套的对象具有必须与同级属性函数cargument的类型匹配的键

如何使用泛型函数参数定义类型脚本函数

基于泛型的类型脚本修改类型

TypeScript:如何使用泛型和子类型创建泛型默认函数?

我如何键入它,以便具有字符串或数字构造函数的数组可以作为字符串或数字键入s或n

换行TypeScript';s具有泛型类型的推断数据类型

Angular 16:无法覆盖;baseUrl;在tsconfig.app.json中

带有过滤键的 Typescript 映射类型

为什么 Typescript 无法正确推断数组元素的类型?