【问题标题】:Generic function parameter with unknown args具有未知参数的通用函数参数
【发布时间】:2021-06-01 19:59:21
【问题描述】:

使用...args: unknown[] 将泛型参数指定为函数不能很好地发挥作用吗?

// Using TS 4.2.3
const fn = (foo: boolean) => 'hello'

// Ideally, one generic function arg
type Data1<M extends (...args: unknown[]) => unknown> = {
  fn: M
}
const data1: Data1<typeof fn> = {
                   ^^^^^^^^^
  fn,
}
Type '(foo: boolean) => string' does not satisfy the constraint '(...args: unknown[]) => unknown'.
  Types of parameters 'foo' and 'args' are incompatible.
    Type 'unknown' is not assignable to type 'boolean'.

这个TypeScript Playground 示例还演示了将函数拆分为两个通用参数,这可行,但从API 的角度来看并不好。我希望能更好地理解为什么一个有效,而另一个无效。

【问题讨论】:

  • M extends Function?
  • @jonrsharpe 是的,那行得通。我猜它相当于M extends (...args: any[]) =&gt; any。仍然对它为什么不喜欢 ...args: unknown[] 感兴趣 ????。

标签: typescript typescript-generics


【解决方案1】:

这是由于variance而发生的。

declare function fn(foo: boolean): string

type Data1<M extends (...args: unknown[]) => unknown> = {
  fn: M
}

const data1: Data1<typeof fn> = {
                      ^
// Type '(foo: boolean) => string' does not satisfy the constraint '(...args: unknown[]) => unknown'.
//  Types of parameters 'foo' and 'args' are incompatible.
//    Type 'unknown' is not assignable to type 'boolean'.
  fn,
}

您的数据类型Data1 接受一个函数类型在该函数的结果类型中是协变的,在函数的参数类型中是逆变的。把它(Data1)想象成一个接受参数(foo: boolean) =&gt; string 的函数(它在类型级别上的真正含义)。此函数必须检查此参数是否可“分配”到参数类型(...args: unknown[]) =&gt; unknown。要检查此检查器必须确保两件事:

  • 函数的返回类型string 必须可分配给unknown。这是完全可能的。所有类型都可以分配给unknown。

  • 参数的参数类型unknown[] 必须可分配给函数的参数类型[boolean]。这就是检查器引发错误的地方。因为unknown 只能分配给unknown 和any。

当涉及到争论时,为什么它必须以相反的方向发生?因为作为参数传递的函数是数据的消费者。它必须处理比预期作为参数的函数更广泛的类型。简单例子:

function lowerCase(s: string): string {
  return s.toLowerCase()
}

function callCb(cb: (sn: string | number) => string | number): number {
  const stringOrNumber = cb(Math.random() ? 'string' : 10)

  return typeof stringOrNumber === 'string' 
    ? stringOrNumber.length 
    : stringOrNumber  ​
} 

callCb(lowerCase)

TS playground 和 strictFunctionTypes 关闭以说明不健全。

虽然cb 可以返回callCb 的预期参数返回类型(字符串、数字、字符串| 数字)的任何子集,但cb 必须能够处理每个 类型callCb可以提供给它。


type Data2<Args extends unknown [], Return extends unknown> = {
 ​fn: (...args: Args) => Return
}

const data2: Data2<Parameters<typeof fn>, ReturnType<typeof fn>> = {
 ​fn,
}

至于你的Data2 类型。您正在为它提供精确的fn 的参数和结果类型,它们都处于协变/正位置。首先它检查提供的类型是否满足约束:

​* 第一个类型参数 [boolean] 可分配给 unknown[] ​* 第二类参数string 可分配给unknown

然后它会检查您在赋值右侧传递的类型为{ fn: (foo: boolean) =&gt; string } 的对象是否可分配给Data2 的结果类型{ fn: (...[boolean]) =&gt; string }。显然是这样。


您可以通过禁用tsconfig.json 中的strictFunctionTypes 选项来关闭此行为。然后打字稿将函数参数视为双变量。虽然我强烈建议不要这样做。 ​

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-06
    • 1970-01-01
    • 2014-02-27
    • 2018-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-09
    相关资源
    最近更新 更多