Typescript Proxyify Generic - PullRequest
       11

Typescript Proxyify Generic

0 голосов
/ 03 мая 2018

Я пытаюсь обернуть базовый интерфейс, предоставляемый через тип generic, чтобы каждая функция в интерфейсе имела измененное возвращаемое значение.

Например:

interface IBaseInterface {
  test(a?: boolean, b?: number): Promise<boolean>;
  anotherTest?(a?: number): Promise<number>;
}

// to...

interface IBaseInterfaceModified {
  test(a?: boolean, b?: number): Promise<boolean> | string;
  anotherTest?(a?: number): Promise<number> | string;
}

Я безуспешно пытался использовать сопоставленные типы с комбинацией обобщений. Самое близкое, что я получил, это:

type TProxyify<T> = {
  [K in keyof T]: TProxy<T[K]>;
};

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
type TProxy<T> = {
  (): ReturnType<T> | string;
};

export function wrapType<T>(): TProxyify<T> {
  return 1 as any;
}

const s = wrapType<IBaseInterface>();
// s.test() tooltip outputs -> test(): string | Promise<boolean>

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

Любое руководство очень ценится. Спасибо.

1 Ответ

0 голосов
/ 03 мая 2018

См. Ниже для решения 3.0

Вы можете использовать аналогичный подход к ответу типа замены возврата здесь

type TProxyify<T> = {
    [K in keyof T]: AddReturnType<T[K], string>;
};

type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;
type AddReturnType<T, TNewReturn> = T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => infer R ? (
    IsValidArg<J> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => R | TNewReturn :
    IsValidArg<I> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => R | TNewReturn :
    IsValidArg<H> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => R | TNewReturn :
    IsValidArg<G> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => R | TNewReturn :
    IsValidArg<F> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F) => R | TNewReturn :
    IsValidArg<E> extends true ? (a: A, b: B, c: C, d: D, e: E) => R | TNewReturn :
    IsValidArg<D> extends true ? (a: A, b: B, c: C, d: D) => R | TNewReturn :
    IsValidArg<C> extends true ? (a: A, b: B, c: C) => R | TNewReturn :
    IsValidArg<B> extends true ? (a: A, b: B) => R | TNewReturn :
    IsValidArg<A> extends true ? (a: A) => R | TNewReturn :
    () => R | TNewReturn
) : T

export function wrapType<T>(): TProxyify<T> {
    return 1 as any;
}

interface IBaseInterface {
    test(a?: boolean, b?: number): Promise<boolean>;
    anotherTest?(a?: number): Promise<number>;
}

const s = wrapType<IBaseInterface>();

let ss = s.test(undefined, undefined); // will be string | Promise<boolean>

Проблема этого подхода заключается в том, что при использовании с дополнительными параметрами необязательный параметр становится обязательным (и имеет тип A | undefined). Вот почему вызов теста s.test(undefined, undefined);, а не s.test();

Также не сохраняются имена параметров, что может быть проблемой для удобства чтения.

Редактировать

Поскольку первоначальный вопрос был дан, машинопись улучшила возможное решение этой проблемы. С добавлением кортежей в параметрах отдыха и выражениях нам теперь не нужно иметь все перегрузки:

type TProxyify<T> = {
    [K in keyof T]: AddReturnType<T[K], string>;
};

type ArgumentTypes<T> = T extends (... args: infer U ) => any ? U: never;
type AddReturnType<T, TNewReturn> = T extends (...args: any[])=> infer R ? (...a: ArgumentTypes<T>) => TNewReturn | R : T;

export function wrapType<T>(): TProxyify<T> {
    return 1 as any;
}

interface IBaseInterface {
    test(a?: boolean, b?: number): Promise<boolean>;
    anotherTest?(a?: number): Promise<number>;
}

const s = wrapType<IBaseInterface>();

let ss = s.test(undefined, undefined); // will be string | Promise<boolean>

Это не только короче, но и решает ряд проблем

  • Необязательные параметры остаются необязательными
  • Имена аргументов сохраняются
  • Работает для любого количества аргументов
...