javascript - 函数定义的下一个参数取决于第一个参数

标签 javascript typescript overloading

考虑具有不同参数的简单函数列表:

const fns = {
  isValidDate: (input: string, min?: Date, max?: Date): boolean => {
     // ...
     return true;
  },

  isValidOption: (input: string, options: string[]): boolean => {
     // ...
     return true;
  },

};

它们都返回相同的类型(bool);

然后是应该调用上述任何函数的另一个函数:

function validateField(where: string, fn: keyof typeof fns, ...args: any[]){
   // ...
   return fns[fn](...args);
}

如何使 args 反射(reflect)所选 fn 的参数?

例如:

validateField("test", "isValidDate", new Date()); // should be ok 
validateField("test", "isValidDate", 123); // should fail

并在 vscode 提示中显示参数,就像普通函数一样。

我知道我需要为每个 fn 创建 validateField 的重载,但是如何使用类型定义或其他东西来做到这一点......而不必手动定义每个重载并使用这些参数编写重复代码

最佳答案

您可能希望 validateField()genericfn 参数的类型中,以便您可以为 args 选择合适的类型。我们可以编写一些辅助实用程序类型来计算:

type Fns = typeof fns;

type FnArgs = { [K in keyof Fns]: 
  Fns[K] extends (input: string, ...args: infer A) => boolean ? A : never 
};

/* type FnArgs = {
    isValidDate: [min?: Date | undefined, max?: Date | undefined];
    isValidOption: [options: string[]];
} */

FnArgs 类型是 mapped type 其中每个键来自 fns 的类型,每个值都是 tuple初始 string 之后的参数(使用 conditional type inference 获取该列表)。

现在您可以给 validateField() 这个调用签名:

declare function validateField<K extends keyof Fns>(
  where: string, fn: K, ...args: FnArgs[K]
): boolean;

当你调用它时它会起作用:

validateField("test", "isValidDate", new Date()); //  okay
validateField("test", "isValidDate", 123); // error! number is not assignable to date
validateField("test", "isValidOption", ["a"]) // okay

不幸的是,validateField() 的实现没有类型检查:

function validateField<K extends keyof Fns>(where: string, fn: K, ...args: FnArgs[K]) {
  return fns[fn](where, ...args); // error!  
  // -----------------> ~~~~~~~
  // A spread argument must either have a tuple type or 
  // be passed to a rest parameter.
}

根本问题是缺乏对相关联合 的直接支持,正如microsoft/TypeScript#30581 中所要求的那样.编译器无法理解 fns[fn] 的类型属于与 args 类型相关的函数类型。错误消息有点神秘,但它来自这样一个事实,即它将 args 视为不适合 fns[fn] 参数的元组类型的联合,它被视为没有公共(public)剩余参数类型的函数类型的联合。

幸运的是,microsoft/TypeScript#47109 中描述了一个推荐的解决方案.我们需要给fns一个新的类型,编译器一眼就能看出是一个对象,其方法的参数与FnArgs直接相关。这是它的样子:

function validateField<K extends keyof Fns>(where: string, fn: K, ...args: FnArgs[K]) {
  const _fns: { [K in keyof Fns]: (str: string, ...args: FnArgs[K]) => boolean } = fns;
  return _fns[fn](where, ...args); // okay
}

_fns 变量是 annotated作为映射类型的方法,对于 Fns 的键中的每个 K 显式地带有一个剩余参数 tpe FnArgs[K]fns 到该变量的赋值成功,因为它是同一类型。

但关键的区别在于 _fns[fn](where, ...args)fns[fn](where, ...args) 失败的地方成功.这是因为编译器一直在跟踪 _fns[fn] 类型和 args 类型之间的通用 K 之间的相关性。

现在您拥有了调用者和函数实现所需的东西!

Playground link to code

关于javascript - 函数定义的下一个参数取决于第一个参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74467067/

相关文章:

javascript - Edge 浏览器 XMLHttpRequest 中止不起作用

javascript - Angular Bootstrap 模态未显示

reactjs - 在TypeScript中 react 组件类型

angular - 将对象数组转换为类实例

c++ - 调用正确的重载父方法

javascript - $(...).serializeJSON 不是函数

javascript - HTML5剪贴板API : paste custom format

node.js - 使用 typescript 创建 Node 模块,并将其作为依赖项导入 : Duplicate identifier (TS2300)

c++ - 谷歌模拟 : Mocked overloaded functions create warning C4373

javascript - 获取 javascript 函数实例本身以启用文档字符串。是否可以?