【问题标题】:Is it possible to wrap a function so that the wrapper has the same arguments plus another argument which is situated after these arguments?是否可以包装一个函数,使包装器具有相同的参数以及位于这些参数之后的另一个参数?
【发布时间】:2021-12-30 19:23:51
【问题描述】:

我的目标是包装一个 API 函数,以便包装器具有与 API 函数相同的参数,然后还有一个额外的最终参数。 API 函数非常通用,因此包装器需要从这个内部函数中获取类型和参数。

我的理由是我需要使用额外的可选参数来增强 API 功能。对于使用此包装函数的其他开发人员来说,将此可选参数作为第一个参数将是一种糟糕的体验。

我目前的尝试如下:

const insideFunc = (a: string): string => {
  return a
}

const wrapperFunc = <F extends (...args: any[]) => any>(
  fn: F
): ((b?: string, ...args: Parameters<F>) => [ReturnType<F>, string]) => {
  return (b?: string, ...args: Parameters<F>):[ReturnType<F>, string] => {
    return [fn(...args), b]
  }
}

这几乎是我需要的,但问题是参数 b 必须在 内部函数的参数之前。

在平行宇宙中,解决方案将在新参数之前包含其余参数,如下所示:

const insideFunc = (a: string): string => {
  return a
}

const wrapperFunc = <F extends (...args: any[]) => any>(
  fn: F
): ((...args: Parameters<F>, b?: string) => [ReturnType<F>, string]) => {
  return (...args: Parameters<F>, b?: string):[ReturnType<F>, string] => { //Observe the difference in argument order.
    return [fn(...args), b]
  }
}

但是,由于其余参数必须是最终参数,因此出现此错误。

有没有其他方法可以解决这个问题,以便内部函数参数可以在列表中排在第一位?

【问题讨论】:

    标签: javascript typescript wrapper


    【解决方案1】:

    在函数的参数列表中,展开必须在其他参数之后。但是,对于元组类型,不是也是如此。

    这意味着你可以声明args 喜欢:

    (...args: [...args: Parameters<F>, b: string])
    

    请注意,此元组的每个成员都已命名,这有助于保留有关原始参数名称的智能提示。

    这确实意味着您必须自己解析args,但这并不难:

    const originalArgs = args.slice(0, -1)
    const b = args[args.length - 1]
    return [fn(...originalArgs), b]
    

    这似乎在使用时有效:

    const insideFunc = (name: string, age: number, likes: string[]): string => {
      return `${name} is ${age} and likes ${likes.join(', ')}`
    }
    
    const fn = wrapperFunc(insideFunc)
    
    console.log(fn(
        'Alex',
        39,
        ['cool stuff', 'awesome things'],
        'and also snowboarding'
    ))
    //-> ["Alex is 39 and likes cool stuff, awesome things", "and also snowboarding"] 
    

    当您将鼠标悬停在此处的 fn 上时,您可以看到参数名称保留在报告的类型中:

    const fn: (
      name: string,
      age: number,
      likes: string[],
      b: string
    ) => [string, string]
    

    Working playground example


    此解决方案的一个问题是,如果 b 是可选参数且未提供。

    好吧,你可以向内部函数询问它的length,它返回它接受的参数数量。

    const originalArgs = args.slice(0, fn.length)
    const b = args[fn.length + 1]
    

    Playground

    但是,如果内部函数具有可选参数,或者采用像 ...args 这样的传播,这显然会使事情复杂化。事实上,我认为这会让你无法知道哪些参数适用于你的内部函数,哪些参数应该在后面。


    我可以推荐一个替代 API 吗?比如:

    fn([original, args, here], extraArg)
    

    这样就可以轻松分辨函数的内容以及额外的内容。我认为再多聪明的元组类型或数组切片都不会给你一个完美的、适用于各种情况的解决方案,而无需将原始 args 与额外的 args 明确分开。

    或者可能是嵌套函数,仅在提供额外参数时才调用内部函数?

    fn(original, args, here)(extraArg)
    

    【讨论】:

    • 嗨,亚历克斯,感谢您的帮助。刚刚意识到此解决方案的一个问题是,如果 b 是一个可选参数并且未提供,则表达式 const originalArgs = args.slice(0, -1) 将删除最后一个内部函数参数。我正在考虑检查最后一个参数的类型,但是如果最后一个内部函数参数是相同的类型,这仍然会出现错误状态。你能看出有什么办法吗?
    • 我在回答结束时添加了一些关于该案例的注释。
    猜你喜欢
    • 2017-07-19
    • 1970-01-01
    • 2018-06-23
    • 2014-10-14
    • 1970-01-01
    • 2013-01-14
    • 2017-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多