Типизация декоратора или заместителя в TypeScript

Нужно взять существующую функцию и сделать вокруг неё обёртку, некоего заместителя, прокси, который будет что-то делать перед вызовом функции, но возвращать её результат как есть и передавать ей все аргументы, как есть. Проблема только в том, чтобы декоратор получился точно такого же типа, как исходная функция, у которой есть множество перегрузок.

Код:

function foo(a: number, b: string): number;
function foo(a: string, b: number): boolean;
function foo(a, b): any {
  if (typeof a === 'number') {
    return 0;
  }
  return !!b;
}

type FooType = typeof foo;
/*
type FooType = {
    (a: number, b: string): number;
    (a: string, b: number): boolean;
}
*/

const fooProxy = (...params: Parameters<typeof foo>) => {
  // Здесь я хочу, например, залоггировать параметры вызова
  console.log('Calling foo with params:', params);
  return foo(...params);
}
/*
const fooProxy: (a: string, b: number) => boolean
*/

const fooProxyTyped: FooType = fooProxy;
/*
Ошибка!

Type '(a: string, b: number) => boolean' is not assignable to type '{ (a: number, b: string): number; (a: string, b: number): boolean; }'.
  Types of parameters 'a' and 'a' are incompatible.
    Type 'number' is not assignable to type 'string'.
*/

Вопрос

Как мне объявить fooProxy так, чтобы её typeof fooProxy был идентичен FooType, не повторяя при этом декларации всех перегрузок foo для fooProxy?


Ответы (1 шт):

Автор решения: qwabra

без комментариев. обрати внимание на // (parameter) params:

q: {
    type foo =
        & ((a: number, b: string) => number)
        & ((a: string, b: number) => boolean)
        /** important any end type*/
        & ((a: any, b: any) => any)
    // ;;
    const foo: foo = null as any
    // ;;
    { let res = foo(1, ''), ch: number = res }
    { let res = foo('', 1), ch: boolean = res }
    { let res = foo('', ''), ch: any = res }
    // ;;
    qq: {
        type FooType = typeof foo;
        qqq: {
            const fooProxy = (...params: Parameters<FooType>): ReturnType<FooType> => foo(...params)
            // const fooProxy: (a: any, b: any) => any
        }
        // (parameter) params: [any, any]
        const fooProxy: FooType = (...params: Parameters<FooType>): ReturnType<FooType> => foo(...params)
        // const fooProxy: foo
        const fooProxyTyped: FooType = fooProxy;
        // const fooProxyTyped: foo
    }
}
q: {
    /** important any start interface*/
    interface foo { (a: any, b: any): any }
    interface foo { (a: number, b: string): number }
    interface foo { (a: string, b: number): boolean }
    // ;;
    const foo: foo = null as any
    // ;;
    { let res = foo(1, ''), ch: number = res }
    { let res = foo('', 1), ch: boolean = res }
    { let res = foo('', ''), ch: any = res }
    // ;;
    qq: {
        type FooType = typeof foo;
        qqq: {
            // (parameter) params: [any, any]
            const fooProxy = (...params: Parameters<FooType>): ReturnType<FooType> => foo(...params)
            // const fooProxy: (a: string, b: number) => boolean
        }
        qqq: {
            // (parameter) params: [string, number]
            const fooProxy: FooType = (...params: Parameters<FooType>): ReturnType<FooType> => foo(...params)
            // const fooProxy: foo
        }
        const fooProxy = ((...params: Parameters<FooType>): ReturnType<FooType> => foo(...params)) as FooType
        // const fooProxy: foo
        const fooProxyTyped: FooType = fooProxy;
        // const fooProxyTyped: foo
    }
}
q: {
    function foo(a: number, b: string): number;
    function foo(a: string, b: number): boolean;
    /** important */
    function foo(a: any, b: any): any;
    function foo(a: any, b: any) { return null as any }
    // ;;
    { let res = foo(1, ''), ch: number = res }
    { let res = foo('', 1), ch: boolean = res }
    { let res = foo('', ''), ch: any = res }
    // ;;
    qq: {
        type FooType = typeof foo;
        qqq: {
            const fooProxy = (...params: Parameters<FooType>): ReturnType<FooType> => foo(...params)
            // const fooProxy: (a: any, b: any) => any
        }
        const fooProxy = ((...params: Parameters<FooType>): ReturnType<FooType> => foo(...params)) as FooType
        // const fooProxy: {
        //     (a: number, b: string): number;
        //     (a: string, b: number): boolean;
        //     (a: any, b: any): any;
        // }
        const fooProxyTyped: FooType = fooProxy;
        // const fooProxyTyped: {
        //     (a: number, b: string): number;
        //     (a: string, b: number): boolean;
        //     (a: any, b: any): any;
        // }
    }
}

Для невнимательных

q: {
    type foo =
        & ((a: number, b: string) => number)
        & ((a: string, b: number) => boolean)
        & ((a: any, b: any) => any)
    // ;;
    const foo: foo = null as any
    const foo2: foo = foo
    // ;;
    { let res = foo2(1, ''), ch: number = res }
    { let res = foo2('', 1), ch: boolean = res }
    { let res = foo2('', ''), ch: any = res }
}

Для тех, кому нужно расширить обёртку

q: {
    type foo =
        & ((a: number, b: string) => number)
        & ((a: string, b: number) => boolean)
        & ((a: any, b: any) => any)
    // ;;
    const foo: foo = null as any
    const foo2: foo = foo
    // ;;
    { let res = foo2(1, ''), ch: number = res }
    { let res = foo2('', 1), ch: boolean = res }
    { let res = foo2('', ''), ch: any = res }
}

q: {
    interface foo1 { (a: any, b: any): any }
    interface foo2 { (a: number, b: string): number }
    interface foo3 { (a: string, b: number): boolean }
    /**import foo1 last */
    interface foo extends foo2, foo3, foo1 { }
    // ;;
    const foo: foo = null as any
    const foo2: foo = foo
    // ;;
    { let res = foo2(1, ''), ch: number = res }
    { let res = foo2('', 1), ch: boolean = res }
    { let res = foo2('', ''), ch: any = res }
    // ;;
    qq: {
        qqq: {
            type fooParams = Parameters<foo>
            // type fooParams = [string, number]
            type fooeturnType = ReturnType<foo>
            // type fooeturnType = boolean
        }
        qqq: { // РАСШИРЯЕМ ТУТ
            type extendParams = [{}, any[]]
            type extendReturnType = 'E_X_T_E_N_D'

            /**type fooParams = [any, any] | [number, string] | [string, number] */
            type fooParams = Parameters<foo1> | Parameters<foo2> | Parameters<foo3> | extendParams

            /**type fooReturnType = any */
            // type fooReturnType = ReturnType<foo1> | ReturnType<foo2> | ReturnType<foo3>

            type fooReturnType<T extends fooParams> =
                T extends Parameters<foo2>
                ? ReturnType<foo2>
                : T extends Parameters<foo3>
                ? ReturnType<foo3>
                : T extends extendParams
                ? extendReturnType
                /** important */
                : T extends Parameters<foo1>
                ? ReturnType<foo1>
                : never

            const foo2: <T extends fooParams>(...params: T & fooParams) => fooReturnType<T> = null as any;
            // ;;
            { let res = foo2(1, ''), ch: number = res }
            { let res = foo2('', 1), ch: boolean = res }
            { let res = foo2('', ''), ch: any = res }
            { let res = foo2({}, []), ch: extendReturnType = res/**let res: "E_X_T_E_N_D" */ }
        }
    }
}
→ Ссылка