英文:
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
,但是我得到的是a0
和a1
都是number | boolean
。
有什么解决办法吗?
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
,但是我得到的是a0
和a1
都是number | boolean
。
英文:
I am trying to properly type a callback function. I distilled my use case to this:
const func = <Args extends any[]>(
args: Args,
callback: (...args: Args) => void
) => callback(...args);
func([1, true], (a0, a1) => {
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
any idea how to resolve this?
const func = <Args extends any[]>(
args: Args,
callback: (...args: Args) => void
) => callback(...args);
func([1, true], (a0, a1) => {
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]
,我们有两个选项:
- const assertion(
const 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
});
- 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
});
英文:
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 ['str']
const arr = ['str']
// {name: string} not {name: 'str'}
const obj = {
name: 'str'
}
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:
- 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 '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[]'
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 = <Args extends readonly any[]>(
args: Args,
callback: (...args: Readonly<Args>) => void,
) => callback(...args);
Testing:
func([1, true] as const, (a0, a1) => {
console.log(a0, a1); // a0: 1, a1: true
});
- 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 = <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
});
答案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) => {
console.log(a0, a1);
});
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论