【问题标题】:Passed function without arguments doesn't throw an error不带参数的传递函数不会引发错误
【发布时间】:2021-10-19 03:06:08
【问题描述】:

我使用的是 TypeScript 4.3.5。在代码中,我需要传递一个函数,并且我需要检查该函数是否包含所需的参数。此代码不会引发语法错误:

function functionA (call: (name: string) => boolean) {
  call("")
}

function functionB () {
  return true;
}

function functionC() {
  functionA(functionB); // this doesn't throw a syntax error
  functionB(""); // this throws a syntax error
}

我也试过这种类型定义:

type FunctionB = {
  (name: string): boolean
}

没有任何效果。当我调用 functionA(functionB) 时,我希望 TypeScript 会引发语法错误,因为 functionB 不包含预期的参数。当我调用functionB("") 时,它会抛出一个错误。

当我调用functionA(functionB) 时,有什么解决方法可以解决语法错误吗?

【问题讨论】:

  • 可能有助于理解stackoverflow.com/questions/59135229/…的行为
  • 请记住,您的functionB 实际上确实接受参数,就像任何用户定义的Javascript函数一样。 Javascript 函数总是接受任意数量的参数,并且简单地忽略无关的参数或用undefined 填充以使其工作。这就是语言的工作方式,Typescript 旨在与 Javascript 语义一起使用。

标签: typescript


【解决方案1】:

不,这是预期行为,您不能强制 TypeScript 在这种情况下抛出语法错误。

根据 TypeScript 的FAQ

这是预期和期望的行为。首先,[...] [functionB] 是 [functionA] 的有效参数,因为它可以安全地忽略额外参数。

其次,让我们考虑另一种情况:

let items = [1, 2, 3];
items.forEach(arg => console.log(arg));

这与“想要”错误的示例同构。在运行时,forEach 使用三个参数(值、索引、数组)调用给定的回调,但大多数时候回调只使用一个或两个参数。这是一种非常常见的 JavaScript 模式,必须显式声明未使用的参数会很麻烦。

但是 forEach 应该只将其参数标记为可选!例如forEach(callback: (element?: T, index?: number, array?: T[]))

这不是可选回调参数的含义。函数签名总是从调用者的角度读取。如果 forEach 声明它的回调参数是可选的,则其含义是“forEach 可能调用带有 0 个参数的回调”。

可选回调参数的含义是这样的:

// Invoke the provided function with 0 or 1 argument
function maybeCallWithArg(callback: (x?: number) => void) {
    if (Math.random() > 0.5) {
        callback();
    } else {
        callback(42);
    } 
} 

forEach 总是为其回调提供所有三个参数。您不必检查 index 参数是否未定义 - 它始终存在;这不是可选的。

正如@Liad's answer 中所解释的,这种模式用于许多 JavaScript 函数,无论是原生函数还是其他函数(想想 Array#forEachArray#mapArray#reduce),并且要求函数的用户声明未使用的参数将是“如常见问题解答条目中所述,充其量是繁重的。

因此,functionB 可以赋值给call,只要它共享它的返回类型并且不添加任何更多参数,但声明更少的参数不会引发错误。

至于是否可以强制 TypeScript 编译器抛出语法错误,这也在上述常见问题条目的底部进行了解决:

TypeScript 中目前没有办法指示回调 参数必须存在。请注意,这种强制执行不会 永远直接修复错误。换句话说,在一个假设的世界里 forEach 回调必须接受至少一个参数, 你会有这个代码:

[1, 2, 3].forEach(() => console.log("just counting"));
             //   ~~ Error, not enough arguments?

这将是“固定的”,但不会更正确,通过添加一个参数:

[1, 2, 3].forEach(x => console.log("just counting"));
               // OK, but doesn't do anything different at all

【讨论】:

  • 我理解答案,这很有意义,理想的解决方案可能是添加强制控制回调函数的可能性。将保留默认行为,并且还将涵盖 OP 情况。也许像functionA (call: (name!: string) => boolean)
【解决方案2】:

据我所知这是不可能的,这是有原因的,让我解释一下:

假设你想像这样使用array map 函数:

const double: number[] = [1,5,3,4].map((num) => num * 2)

map 函数需要 3 个参数(typescript 4.3.5 声明):

map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];

如果你的建议适用,我上面写的代码将不起作用,我将始终必须提供所有三个参数,如下所示:

const double: number[] = [1,5,3,4].map((num, i, arr) => num * 2)

这会很乏味...

这也是以下表达式解析为true的原因:

type ArgFunc = (arg: string) => boolean;
type NoArgFunc = () => boolean;

type DoesExtend = NoArgFunc extends ArgFunc ? true : false; // true

但相反的解析为false:

type DoesExtend = ArgFunc extends NoArgFunc ? true : false; // false

【讨论】:

    【解决方案3】:

    Ts Playground

    Typescript 条件运算符来拯救...有点?

    type FnStrongTypedOrNever<T extends (arg: any) => any, TConstraint extends any[]> =
        [...Parameters<T>, ReturnType<T>] extends TConstraint ? T : never;
    
    function functionA<T extends (arg: any) => any>(call: FnStrongTypedOrNever<T, [string, boolean]>) {
      call("")
    }
    
    function functionB() {
      return true;
    }
    
    function functionD(name: string) {
      return true;
    }
    
    function functionC() {
      functionA(functionD); // this doesn't throw a syntax error
      functionA(functionB); // this throws a syntax error
      functionB(""); // this throws a syntax error
    }
    

    使用打字稿的扩展语法的快速而肮脏的解决方案。加上一些额外的类型魔法。

    如果函数不遵守约束,FnStrongTypedOrNever&lt;T, TConstraint&gt; 类型将返回 never

    所以,归根结底...您收到的错误是Argument of type '() =&gt; boolean' is not assignable to parameter of type 'never'

    目前这是你可以用 TS 做的最好的事情(据我所知)

    编辑:

    更新solution

    这个通过TConstraint &amp; '' 使用而不是never 有更好的错误消息

    现在您可以清楚地看到参数列表不匹配。

    这仍然是一种 hack,据我所知,您无法更改环境类型以使其全局工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-07-31
      • 1970-01-01
      • 1970-01-01
      • 2022-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多