Typescript - 在保留签名的同时包装函数

Posted

技术标签:

【中文标题】Typescript - 在保留签名的同时包装函数【英文标题】:Typescript - wrapping functions while preserving signatures 【发布时间】:2020-03-31 10:04:51 【问题描述】:

我试图弄清楚如何包装定义的函数,以便在保留其签名的同时做额外的工作。这是想要的效果:

程序员定义接口:

const actions = 
    first: (id: number) => /*...*/,
    second: (name: string) => /*...*/

let actionsInterface = wrap(actions)
export actionsInterface

actionsInterface应该(即目标)具有以下界面:


    first: (id: number) => void,
    second: (name: string) => void

它基本上提供了与第一次定义相同的接口(即相同的函数列表,具有相同的参数,不计算返回类型),但还有一些额外的工作正在进行中,由@987654324 注入@。

我目前的实现是这样的:

type VarFn = (...args: any) => any

function wrap<T, K extends keyof T>
(funcList: Record<K, T[K] extends VarFn ? T[K] : never>) 

    // this maps a specific function to a function that does something extra
    function wrapOne<T extends (...args: any)=>any>(fn: T) 
        return (...args: Parameters<typeof fn>) => 
            someMagicThingyExtra(fn(args))
        
    

    // we iterate through the list and map each function to the one that's doing something extra
    type FuncMap = Record<K, (...args: Parameters<T[K] extends VarFn ? T[K] : never>)=>void>
    let map: FuncMap
    for (var Key in funcList) 
        let func = funcList[Key]
        map[Key] = wrapOne(func)
    
    return map

但是,我在wrap(actions) 上收到以下错误:

Argument of type ' first: (id: number) => void; second: (name: string) => void; ' is not assignable to parameter of type 'Record<"first" | "second", never>'.
  Types of property 'first' are incompatible.
    Type '(id: number) => void' is not assignable to type 'never'.

所以,由于某种原因,(id: number) =&gt; void(...args: any) =&gt; any 不匹配,因此推断为 never


所以我尝试了一些不同的东西:

function wrap2<T, K extends keyof T, U extends VarFn>
(funcList: Record<K, U>) 

    function wrapOne<T extends (...args: any)=>any>(fn: T) 
        return (...args: Parameters<typeof fn>) => 
            someMagicThingyExtra(fn(args))
        
    

    type FuncMap = Record<K, (...args: Parameters<U>)=>void>
    let map: FuncMap
    for (var Key in funcList) 
        let func = funcList[Key]
        map[Key] = wrapOne(func)
    
    return map

没有错误,但我的 wrap2(actions) 返回类型是:


    first: (...args: any) => void
    second: (...args: any) => void

...我丢失了参数类型,这违背了尝试包装功能但保留签名(即参数类型)的整个目的。

欢迎任何帮助或指导。谢谢!


编辑:

Dragomir 提供了完全保留签名(参数类型和返回类型)的答案。我的用例还需要将返回类型更改为void,这就是我实现它的方式:

function wrap<T extends Record<keyof T, (...args: any)=>any>>(funcList: T) 

    // this maps a specific function to a function that does something extra
    function wrapOne<T extends (...args: any) => any>(fn: T) 
        return ((...args: Parameters<typeof fn>): void => 
            someMagicThingyExtra(fn(args))
        )
    

    // we iterate through the list and map each function to the one that's doing something extra
    type WrapMap = 
        [K in keyof T]: (...args: Parameters<T[K]>)=>void
    
    let map: WrapMap
    for (var Key in map) 
        map[Key] = wrapOne(funcList[Key])
    
    return map

【问题讨论】:

【参考方案1】:

你的泛型类型T 应该有一个约束,它的所有成员都是VarFn 类型,你可以很容易地使用T extends Record&lt;keyof T, VarFn&gt;。由于返回的类型与输入类型完全相同,map 只能是 T 类型。

type VarFn = (...args: any) => any

function wrap<T extends Record<keyof T, VarFn>>(funcList: T) 

  // this maps a specific function to a function that does something extra
  function wrapOne<T extends (...args: any) => any>(fn: T): T 
    return ((...args: Parameters<typeof fn>) => 
      return someMagicThingyExtra(fn(args))
    ) as T
  

  // we iterate through the list and map each function to the one that's doing something extra
  let map =  as T
  for (var Key in funcList) 
    let func = funcList[Key]
    map[Key] = wrapOne(func)
  
  return map


const actions = 
  first: (id: number) => /*...*/ ,
  second: (name: string) => /*...*/ 

let actionsInterface = wrap(actions)

Playground Link

【讨论】:

我印象深刻的是 if 设法推断出正确的函数类型,而不是将它们保留为 VarFn。我猜想扩展整个 Record 而不仅仅是它的值部分就可以了。这确实解决了我的问题。再一次,我返回的函数应该在包装后返回void。那怎么办?谢谢。 我成功地将返回类型更改为void。编辑过的问题也包含在内,它基于您的回答。谢谢!

以上是关于Typescript - 在保留签名的同时包装函数的主要内容,如果未能解决你的问题,请参考以下文章

在 Sphinx 文档中保留包装/装饰 Python 函数的默认参数

AWS Lambda Callback的函数签名

TypeScript - 如何将索引签名表示为泛型类型

Typescript - 具有通用类型函数的索引签名

在 TypeScript 中扩展和定义函数签名

如何在 javascript/typescript 事件回调中访问它,同时保留 removeEventListener 的能力? [复制]