在TypeScript中正确编写回调函数的类型

huangapple go评论79阅读模式
英文:

Properly typing callback functions in typescript

问题

我正在尝试正确地定义回调函数。我将我的用例简化为以下代码:

const func = <Args extends any[]>(
  args: Args,
  callback: (...args: Args) => void
) => callback(...args);

func([1, true], (a0, a1) => {
  console.log(a0, a1);
});

我希望a0的类型是number,而a1的类型是boolean,但是我得到的是a0a1都是number | boolean

screenshot

有什么解决办法吗?

const func = <Args extends any[]>(
  args: Args,
  callback: (...args: Args) => void
) => callback(...args);

func([1, true], (a0, a1) => {
  console.log(a0, a1);
});

我希望a0的类型是number,而a1的类型是boolean,但是我得到的是a0a1都是number | boolean

英文:

I am trying to properly type a callback function. I distilled my use case to this:

const func = &lt;Args extends any[]&gt;(
  args: Args,
  callback: (...args: Args) =&gt; void
) =&gt; callback(...args);

func([1, true], (a0, a1) =&gt; {
  console.log(a0, a1);
});

I want the type of a0 to be number and the type of a1 to be boolean however i am getting both a0, and a1 as number | boolean

screenshot

any idea how to resolve this?

const func = &lt;Args extends any[]&gt;(
  args: Args,
  callback: (...args: Args) =&gt; void
) =&gt; callback(...args);

func([1, true], (a0, a1) =&gt; {
  console.log(a0, a1);
});

I want the type of a0 to be number and the type of a1 to be boolean however i am getting both a0, and a1 as number | boolean

答案1

得分: 1

问题出在编译器对非原始类型的默认行为上。它会扩展数组和对象的类型,以便更容易处理它们:

// string[] 而不是 ['str']
const arr = ['str']

// {name: string} 而不是 {name: 'str'}
const obj = {
   name: 'str'
}

然而,在某些情况下,这并不是我们想要的:

// (number | boolean)[]
const arr = [1, true]

为了使其实际上是 [1, true],我们有两个选项:

  1. const assertionconst assertion):
    const assertion 防止编译器扩展变量的类型并使其为只读:
// readonly [1, true]
const arr = [1, true] as const

如果我们尝试将 [1, true] 传递给该函数,将会出现错误:

Argument of type 'readonly [1, true]' is not assignable to parameter of type 'any[]'.
  The type 'readonly [1, true]' is 'readonly' and cannot be assigned to the mutable type 'any[]'

问题在于只读数组不能赋值给可变数组,因为只读数组是可变数组的超集

// false
type Case1 = readonly any[] extends any[] ? true : false
// true
type Case2 = any[] extends readonly any[] ? true : false

因此,我们需要更新func中的约束条件:

const func = <Args extends readonly any[]>(
  args: Args,
  callback: (...args: Readonly<Args>) => void,
) => callback(...args);

测试:

func([1, true] as const, (a0, a1) => {
  console.log(a0, a1); // a0: 1, a1: true
});
  1. const type parameter Typescript >= 5.0
    常量类型参数允许我们执行与const assertion相同的操作。因此,如果我们将文字对象/数组传递给函数,它不会扩展:
const func = <const Args extends readonly any[]>(
  args: Args,
  callback: (...args: Readonly<Args>) => void,
) => callback(...args);

func([1, true], (a0, a1) => {
  console.log(a0, a1); // a0: 1, a1: true
});

playground

英文:

The problem is with the default behavior of the compiler with the non-primitive types. It widens the type for arrays and objects, which is totally okay to make working with them easier:

// string[] not [&#39;str&#39;]
const arr = [&#39;str&#39;]

// {name: string} not {name: &#39;str&#39;}
const obj = {
   name: &#39;str&#39;
}

However, in some cases it is not what we want:

// (number | boolean)[]
const arr = [1, true]

To make it actually [1, true] we have two options:

  1. const assertion:
    const assertion prevents the compiler from widening the type of the variable and makes it readonly:
// readonly [1, true]
const arr = [1, true] as const

If we try to pass the [1, true] to the func, we will get the error:

Argument of type &#39;readonly [1, true]&#39; is not assignable to parameter of type &#39;any[]&#39;.
  The type &#39;readonly [1, true]&#39; is &#39;readonly&#39; and cannot be assigned to the mutable type &#39;any[]&#39;

The issue in here is that readonly arrays are not assignable to mutable arrays, since readonly arrays are supersets for the mutable arrays:

// false
type Case1 = readonly any[] extends any[] ? true : false
// true
type Case2 = any[] extends readonly any[] ? true : false

So, we need to update the constraints in the func:

const func = &lt;Args extends readonly any[]&gt;(
  args: Args,
  callback: (...args: Readonly&lt;Args&gt;) =&gt; void,
) =&gt; callback(...args);

Testing:

func([1, true] as const, (a0, a1) =&gt; {
  console.log(a0, a1); // a0: 1, a1: true
});
  1. const type parameter Typescript >= 5.0:
    Const type parameters allow us to do the same thing that const assertion does. So if we pass literal object/array to the function it won't be widened:
const func = &lt;const Args extends readonly any[]&gt;(
  args: Args,
  callback: (...args: Readonly&lt;Args&gt;) =&gt; void,
) =&gt; callback(...args);

func([1, true], (a0, a1) =&gt; {
  console.log(a0, a1); // a0: 1, a1: true
});

playground

答案2

得分: 0

类型推断在这里不起作用,因为数组总是被推断为联合类型((number|boolean)[])而不是元组([number, boolean])。

将类型断言为元组可以解决您的问题:

func([1, true] as [number, boolean], (a0, a1) => {
  console.log(a0, a1);
});
英文:

Type inference isn't helping you here as array are always infered as unions ((number|boolean)[])not tuples ([number, boolean]).

The type assertion to a tuple fixes your issue:

func([1, true] as [number, boolean], (a0, a1) =&gt; {
  console.log(a0, a1);
});

huangapple
  • 本文由 发表于 2023年7月24日 16:40:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76752745.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定