在TypeScript中,如何编写回调可以位于多个参数位置的函数?

在TypeScript中,如何编写回调可以位于多个参数位置的函数?,typescript,Typescript,这是我到目前为止所做的,但是对于bar[key](请参阅)抛出了一个错误 我希望能够使用test(“hello”、{foo:bar}、()=>{})和test(“hello”、()=>{})调用test type TestCallback=()=>void 接口条{ [键:字符串]:字符串 } 常数测试=函数( 傅:字符串, bar?:bar | TestCallback | null, 回调?:TestCallback ) { if(条的类型==“函数”){ 回调=条 bar=null } i

这是我到目前为止所做的,但是对于
bar[key]
(请参阅)抛出了一个错误

我希望能够使用
test(“hello”、{foo:bar}、()=>{})和
test(“hello”、()=>{})调用
test

type TestCallback=()=>void
接口条{
[键:字符串]:字符串
}
常数测试=函数(
傅:字符串,
bar?:bar | TestCallback | null,
回调?:TestCallback
) {
if(条的类型==“函数”){
回调=条
bar=null
}
if(对象的条形图和条形图实例){
Object.keys(bar).forEach(函数(key){
console.log(键,条[key])
})
}
如果(回调){
回调函数()
}
}

您的问题与您使用
bar
函数参数的方式有关,默认情况下TS将参数视为常量,但当我们更改参数时,您通过设置
bar=null
将其视为
let
。这种差异在下面可以看到:

{
  // with let function declaration has not narrowed type
  let a: string | number = 1;
  if (typeof a === 'number') {
    a // a is number yep
    const f = () => {
      a // a is not to be believed so it is string | number
    }
  }
}
{
  // with const function declaration has narrowed type
  const a: string | number = 1;
  if (typeof a === 'number') {
    a // a is number yep
    const f = () => {
      a // a is still number as it is const
    }
  }
}
因此,当我们在类型内使用
const
函数声明时,guard会将此类型视为缩小,但使用
let
时,它不信任它。为什么?因为例如,函数可以异步调用,变量可以在之前更改,所以TS对
let
更具防御性,以防止运行时错误

这种情况适用于您的情况,通过重新分配参数
bar
TS将其切换到
let
模式。我们可以通过删除重新分配来修复它。考虑:

const test = function(
  foo: string,
  bar?: Bar | TestCallback,
  callback?: TestCallback
) {
  if (bar && typeof bar !== 'function') {
    Object.keys(bar).forEach((key) => {
      console.log(key, bar[key]) // no error bar is Bar
    })
  }
  if (callback) {
    callback()
  }
}

其他想法


关于这种行为的一些想法,以及它是否是错误的。它的主观性,我们需要了解很多因素,这其中也有一个需要快速编译的因素。TS在这里选择的是直截了当的方法,你重新分配,TS对你的类型不太相信。在像您这样的情况下,这可能是一种负担,但仍有许多其他情况受益于这种安全性

您的问题与您使用
bar
函数参数的方式有关,默认情况下TS将参数视为常量,但当我们更改参数时,您通过设置
bar=null
将其视为
let
。这种差异在下面可以看到:

{
  // with let function declaration has not narrowed type
  let a: string | number = 1;
  if (typeof a === 'number') {
    a // a is number yep
    const f = () => {
      a // a is not to be believed so it is string | number
    }
  }
}
{
  // with const function declaration has narrowed type
  const a: string | number = 1;
  if (typeof a === 'number') {
    a // a is number yep
    const f = () => {
      a // a is still number as it is const
    }
  }
}
因此,当我们在类型内使用
const
函数声明时,guard会将此类型视为缩小,但使用
let
时,它不信任它。为什么?因为例如,函数可以异步调用,变量可以在之前更改,所以TS对
let
更具防御性,以防止运行时错误

这种情况适用于您的情况,通过重新分配参数
bar
TS将其切换到
let
模式。我们可以通过删除重新分配来修复它。考虑:

const test = function(
  foo: string,
  bar?: Bar | TestCallback,
  callback?: TestCallback
) {
  if (bar && typeof bar !== 'function') {
    Object.keys(bar).forEach((key) => {
      console.log(key, bar[key]) // no error bar is Bar
    })
  }
  if (callback) {
    callback()
  }
}

其他想法


关于这种行为的一些想法,以及它是否是错误的。它的主观性,我们需要了解很多因素,这其中也有一个需要快速编译的因素。TS在这里选择的是直截了当的方法,你重新分配,TS对你的类型不太相信。在像您这样的情况下,这可能是一种负担,但仍有许多其他情况受益于这种安全性

这似乎是TypeScript编译器做出的一个奇怪的决定

  • bar
    不是不可变的,因此它可以更改
  • 您在另一个函数中定义了
    bar[key]
    (对
    .forEach的回调)
  • 编译器似乎认为,正因为如此,
    bar
    在函数执行时可能已经发生了变化

    这是不正确的-函数是同步定义和使用的,因此不可能在定义和调用之间重新分配
    bar

    尽管如此,编译器仍在抱怨

    这里有一些避免的方法

    类型断言(不推荐) 只要否决编译器:

    if (bar && bar instanceof Object) {
      Object.keys(bar).forEach(function(key) {
        console.log(key, (bar as Bar)[key])
      })
    }
    

    这是最简单的一个,但如果您将
    bar
    的类型更改为其他类型
    bar?:bar | Baz | TestCallback | null
    ,并且忘记更新类型断言,则可能会出现问题。编译器不会抱怨,因此很容易忽略它

    重复类型防护装置(不推荐)

    这还将确保编译器在使用
    bar
    时,它实际上是
    bar
    类型。然而,这是一个无用的检查,因为它不应该是必要的。这使得代码更难阅读和理解

    使用
    const

    不能重新分配
    const
    ,因此编译器将知道
    baz
    只能是类型guards之后的
    Bar
    ,并且在函数执行时不会更改

    仍然添加了额外的一行,但至少比直接推翻编译器或重新评估
    条的类型更明智

    提取
    forEach
    回调并将其咖喱化 这是一个选项,但可能不会每次都有用

    const f = (obj: Bar) => (key: keyof Bar) => {
      console.log(key, obj[key])
    }
    
    /* ... */
    
    if (bar && bar instanceof Object) {
      Object.keys(bar).forEach(f(bar))
    }
    

    这类似于使用
    const
    ,因为它将
    bar
    捕获到编译器不会抱怨可能更改的不同变量中。另一方面,这可能是一个过度的任务。然而,如果您已经想要函数,它在某些情况下可能更有用

    用于…的
    (推荐) 这仍然是一种绕过编译器错误解释的方法,只需消除错误解释的原因-删除函数定义:

    if (bar && bar instanceof Object) {
      for (let key of Object.keys(bar)) {
        console.log(key, bar[key]);
      }
    }
    


    如果没有函数定义,编译器只能解释在检查其类型和使用时间之间,
    bar
    不会改变。

    这似乎是TypeScript编译器的一个奇怪决定

  • bar
    不是不可变的,因此它可以更改
  • 您在另一个函数中定义了
    bar[key]
    (对
    .forEach的回调)
  • 编译器似乎认为