Website-Suche

Typescript-Funktionsüberladung mit Objekt - Implementierungssignaturen von Überladungen sind extern nicht sichtbar


Problem

Wie kann ich eine Überladungs-Implementierungssignatur korrekt exponieren?

Beispiel

Aufbauend auf dieser Frage:

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 [];
}

Wie kann ich diese Funktion umhüllen, aber die möglichen Rückgabetypen abhängig vom Optionen-Argument beibehalten?

Zum Beispiel:

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

   return get(options) 
}

Abhängig vom für Optionen in der wrapFunction-Methode festgelegten Wert würde der Rückgabewert auf dem Rückgabetyp von get mit demselben Wert basieren.

d.h. :

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

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

Versuchte Lösungen

Ich könnte einfach eine neue Signatur für die wrapFunction neu schreiben, aber das wäre ziemlich umständlich, besonders wenn ich viele Arten von wrapFunctions habe, die dem gleichen Muster folgen, einen verschachtelten get-Aufruf zu haben.

Ein Vorschlag war, die wrapFunction als typeof get zu typisieren, aber dadurch geht die Möglichkeit verloren, die Parameterliste von wrapFunction zu ändern.

Das könnte damit zusammenhängen.

Verwandte Links

Typescript-Spielplatz-Link

Antwort #1

Lösung

Um dieses Problem zu lösen, habe ich bedingte Typen verwendet. Zum Beispiel:

// 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 })

Der vollständige Vergleich kann hier gesehen werden

Gedanken

Dies sind Umgehungslösungen für dieses Problem, lösen es jedoch ausreichend. Für komplexe Szenarien oder vielleicht eine versteckte API wirkt die Verwendung der Currying-Methode sauberer, da sie die Notwendigkeit vieler Überladungsmethoden vermeidet. Ebenso eignen sich grundlegende Szenarien oder öffentliche APIs eher für die zweite Lösung.

Verwandte Artikel