我想实现以下内容:
创建一个函数F,它接受函数G作为第一个参数,并且:
- 如果G有1个或多个参数,F接受参数列表作为第二个参数
- 如果G有0个参数,则F不接受任何其他参数
我第一次天真地尝试实现它是这样的:
type F0 = () => any
type F1 = (x: any, ...xs: any[]) => any
function f<F extends F0>(f: F): void
function f<F extends F1>(f: F, xs: Parameters<F>): void
这并不十分有效,给出以下结果:
f(() => {}) // ok
f((a: number) => {}, [2]) // ok
f((a: number) => {}, ['a'])
// Error: Type 'string' is not assignable to type 'number'.
// Good, working as expected
f((a: number) => {})
// Error: Argument of type '(a: number) => void' is not assignable to parameter of type 'F0'
// So it sees this as the first overload. It doesn't ask for a missing second arg.
// Technically, suits my goals, but in a kinda wrong way.
f(() => {}, []) // ok (!)
// Somehow it sees this as valid application of the second overload,
// regardless of parameters count
问题是,F0 extends F1 ? true : false
给出了true
的结果-这就是为什么最后一种情况被认为是第二种重载。
我明白这里没有错,这是TS的正确行为。
但到那时我的任务有可能完成吗?
1条答案
按热度按时间dxxyhpgq1#
编写
f()
的调用签名的一种方法如下:它不是显式重载,而是回调函数的rest参数元组类型
A
中的generic,然后我们计算ArgsOrNothing<A>
以确定f()
是否应该接受另一个参数。由于ArgsOrNothing<A>
是一个联合类型,f()
的行为就像一个重载函数。第一种可能性:
[xs: A & [any, ...any]]
,这意味着f()
接受A & [any, ...any]]
类型的附加参数。[any, ...any]
类型是具有rest元素的元组,这意味着必须知道它至少包含一个元素。因此,A & [any, ...any]
类型的值既适合作为cb
的参数元组,* 和 * 必须包含至少一个元素,这意味着这种可能性是用于使用两个参数调用f(cb, xs)
,其中xs
非空。另一种可能性:
([] extends A ? [] : never)
。这是一个条件类型,用于检查空参数列表[]
是否可赋值给A
...换句话说,cb()
是否是可接受的调用。如果是,则f()
应该可以仅使用一个参数进行调用(通过将[]
添加到...rest
的类型)。如果不是,则f()
应该 * 不 * 是仅用一个参数可调用的(通过不将任何东西添加到...rest
的类型;| never
不改变任何东西)。这意味着这种可能性是在cb()
可以接受的情况下,用一个参数调用f(cb)
。让我们来测试一下:
我认为这些看起来都不错,标准的情况可以做你想做的,对于边缘情况,比如
cb()
有可选参数或者它是可变参数,上面的行为是你应该被允许以两种方式调用它,但是如果你想调用底层函数cb()
而不带参数,你就不能给f()
传递第二个参数。Playground代码链接