Typescript - функции обертывания с сохранением подписей

Я пытаюсь понять, как обернуть определенные функции, чтобы я мог выполнять дополнительную работу, сохраняя их подписи. Вот желаемый эффект:

Программист определяет интерфейс:

const actions = {
    first: (id: number) => {/*...*/},
    second: (name: string) => {/*...*/}
}
let actionsInterface = wrap(actions)
export actionsInterface

actionsInterface должен (т. Е. Это цель) иметь следующий интерфейс:

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

Он в основном предоставляет тот же самый интерфейс (то есть тот же список функций, с теми же параметрами, не считая возвращаемого типа), что и был определен вначале, но выполняется дополнительная работа, которая была введена wrap().

Моя текущая реализация выглядит примерно так:

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) => void не совпало с (...args: any) => 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
}

... и я потерял типы параметров, что сводит на нет всю цель попытки обернуть функциональность, но сохранить сигнатуры (то есть типы параметров).

Любая помощь или руководство приветствуются. Спасибо!


РЕДАКТИРОВАТЬ:

Драгомир предоставил ответ, который полностью сохраняет подпись (как типы параметров, так и типы возвращаемых значений). В моем варианте использования также потребовалось изменить тип возвращаемого значения на 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
}

person Abstract Algorithm    schedule 06.12.2019    source источник


Ответы (1)


Общий тип T должен иметь ограничение, что все его члены относятся к типу VarFn, что можно легко сделать с помощью T extends Record<keyof T, VarFn>. Поскольку возвращаемый тип в точности совпадает с типом ввода, 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)

Ссылка на игровую площадку

person Titian Cernicova-Dragomir    schedule 06.12.2019
comment
Я впечатлен тем, что if удается вывести правильный тип функций вместо того, чтобы оставить их как VarFn. Я полагаю, что расширение всего Record, а не только его ценностной части, поможет. Это действительно решает мою проблему. Еще раз, мои возвращенные функции должны возвращать void после упаковки. Как это можно сделать? Спасибо. - person Abstract Algorithm; 06.12.2019
comment
Мне удалось изменить тип возвращаемого значения на void. Отредактировал вопрос, чтобы включить его, и он был основан на вашем ответе. Спасибо! - person Abstract Algorithm; 06.12.2019