Перегрузка функции машинописного текста с помощью объекта — сигнатуры реализации перегрузок не видны извне

Проблема

Как я могу правильно выставить сигнатуру реализации перегрузки?

Пример

На основе этого вопроса:

interface MyMap<T> {
  [id: string]: T;
}

type Options = {
  asObject?: boolean,
  other?: Function
  testing?: number
};


function get(options: { asObject: true, other?: Function, testing?:number }): MyMap<any>;
function get(options: { asObject?: false,  other?: Function, testing?:number }): number[];
function get(): any[];
function get(options: Options = { asObject: false }): any[] | MyMap<any> {
    if (options?.asObject) return {} as MyMap<any>;

    return [];
}

Как я могу обернуть эту функцию, но сохранить возможные типы возвращаемых значений в зависимости от аргумента параметров?

Например:

function wrapFunction(arg1 arg2, options) { 
   // do something with arg1 and arg2

   return get(options) 
}

В зависимости от значения, установленного для параметров в методе wrapFunction, тип возвращаемого значения будет основан на типе возвращаемого значения get с тем же значением.

ie:

const g = wrapFunction(1,2, {asObject: true}) 

// Should return the same thing as get({asObject:true})

Попытки решения

Я мог бы просто переписать новую сигнатуру для функции wrapFunction, но это было бы довольно многословно, особенно если у меня есть много типов функций wrap, которые следуют одному и тому же шаблону вложенного вызова get.

Одно предложение заключалось в следующем: введите функцию wrapFunction как typeof get, но это лишает возможности изменять список параметров функции wrapFunction.

Это может связаны.

Ссылки по теме

Typescript Playground link< /а>


person Justin Dalrymple    schedule 26.01.2021    source источник
comment
Похоже, ссылка на вашу игровую площадку не работает. Но я не понимаю, что вы подразумеваете под «внешним» вызовом кода? Вы имеете в виду, как правильно экспортировать функцию?   -  person Aron    schedule 27.01.2021
comment
@Арон Исправлено! Видите, как я вызываю функцию get из другой функции? Я не был уверен, как лучше всего это описать, кроме того, что называл сигнатуры внешне   -  person Justin Dalrymple    schedule 27.01.2021
comment
Just expose overload with same signature as implementation typescriptlang.org /играть?#код/   -  person Aleksey L.    schedule 27.01.2021
comment
@АлексейЛ. это удаляет специфичность типа. Например, тип b здесь: const b = g({ asObject: true }) будет any[] | MyMap<any>, а не только MyMap<any> из первой подписи.   -  person Justin Dalrymple    schedule 27.01.2021
comment
Он ничего не удаляет, он ведет себя так, потому что g имеет следующую подпись: function g(options?: Options) ... где options?: Options разрешает третью перегрузку   -  person Aleksey L.    schedule 28.01.2021
comment
О, я понимаю, я переформулировал свой вопрос в посте! Посмотрите, имеет ли это больше смысла @AlekseyL.   -  person Justin Dalrymple    schedule 29.01.2021
comment
Вы можете повторно использовать тип исходной функции. Not sure if that helps: typescriptlang.org/play?# код/   -  person Aleksey L.    schedule 29.01.2021
comment
Определенно ближе к тому, что я ищу! Хотя такая техника сломалась бы, если бы аргументы родительской функции были изменены в любом случае, нет? (Я обновлю вопрос примером)   -  person Justin Dalrymple    schedule 29.01.2021


Ответы (1)


Решение

Чтобы решить эту проблему, я использовал условные типы. Например:

// This is the switch interface
interface Switch<T> {
  on: T
}

// This is the conditional type.
type ExtendReturn<E extends boolean, R extends string | number > = E extends true
  ? Array<R>
  : number;

/*
First solution, using the conditional type and type inference. This requires a
nested structure and returns a curry-like function. The inner function separates out all the generics that should be inferred, and the outer function contains the generic that should be set explicitly 
*/
function test3<R extends string | number = string>() { 
  return function inner<E extends boolean>(options: Switch<E>): ExtendReturn<E, R> {
    const r = options.on ? 's' : 4

    return r as ExtendReturn<E, R>
  }
}

// Notice the extra (), but each of the types are inferred correctly
const a = test3<number>()({ on: true })


/*
Second Solution, using a combination of overload methods, and the conditional
type. This is a bit more verbose, but removes the requirement of the currying
structure, keeping a cleaner API.
*/
function test4<R extends string | number = string>(options?: Switch<false>): ExtendReturn<false, R>;
function test4<R extends string | number = string>(options: Switch<true>): ExtendReturn<true, R>;
function test4<E extends boolean, R extends string | number = string>(options?: Switch<E>): ExtendReturn<E, R> {
  const r = options?.on ? 's' : 4

  return r as ExtendReturn<E, R>
}

// Notice the simpler API
const m = test4<string>({ on: true })

The full comparison can be seen here

Мысли

Это обходные пути для этой проблемы, но в достаточной степени решают проблему. Для сложных сценариев или, возможно, скрытого API использование метода каррирования выглядит чище, устраняя необходимость во многих методах перегрузки. Точно так же более простые сценарии или общедоступные API подходят для второго решения.

person Justin Dalrymple    schedule 07.02.2021