Website-Suche

Wie behebt man eine generische TypeScript-Funktion, damit sie einen gültigen abgeleiteten Typ zurückgibt?


Ich habe den folgenden TypeScript-Code (Link zur Spielwiese):

type MyCallback<T> = (s: string, payload: T) => void;

interface IActions {
  do1: MyCallback<number>;
  do2: MyCallback<string>;
  [key: string]: (s: string, payload: any) => void;
}

function convert<T extends { [key: string]: (s: string, payload: any) => void }>(callbackMap: T) {

  const result: { [key: string]: <U>(payload: U) => void } = {};

  Object.keys(callbackMap).forEach(key => {
    if (typeof callbackMap[key] === 'function') {
      result[key] = callbackMap[key].bind(null, "data");
    }
  })

  return result;
}

const maps = convert<IActions>({
  do1: (s: string, payload: number) => {
    //
  },
  do2(s: string, payload: string) {
    //
  }
});

maps.do1(1); // valid
maps.smth("1"); // should be type-check error, but TS thinks it's valid

Was ich zu tun versuche, ist eine Funktion zu erstellen, die ein Objekt über ein Interface akzeptiert. Die Funktion konvertiert alle Methoden des Objekts in ein neues Objekt, bei dem alle Methoden einen festen Parameter haben (über die bind-Methode). Mit anderen Worten, ich möchte dieses Interface konvertieren

interface IActions {
  do1: (state: string, payload: number);
  do2: (state: string, payload: string);
  .....
}

zu

interface IActions {
  do1: (payload: number);
  do2: (payload: string);
  ....
}

Ich möchte es allgemein machen, sodass es jede Schnittstelle basierend auf dem generischen Parameter konvertiert.

Das Problem mit meinem aktuellen Ansatz ist, dass ich keine Intellisense und Typprüfung für mein maps-Objekt habe.

Ist es möglich, meine convert-Funktion so zu ändern, dass der Rückgabewert automatisch anhand des eingehenden Interfaces abgeleitet wird? Mit anderen Worten, ich habe eine vollständige Typüberprüfung und Intellisense für den Rückgabewert (maps in meinem Fall).

Antwort #1

Die Tatsache, dass maps.smth gültig ist, liegt an der expliziten Index-Signatur im Ergebnis. Was Sie hier benötigen, ist ein gemappter Typ, um über die Eigenschaften von IActions auf einen neuen Typ zuzuordnen, der die modifizierten Methoden enthält. Um die neue Methodensignatur zu erstellen, können wir einen bedingten Typ verwenden, um die übrigen Parameter zu extrahieren (den ersten überspringen).

type MyCallback<T> = (s: string, payload: T) => void;

interface IActions {
  do1: MyCallback<number>;
  do2: MyCallback<string>;
}

function convert<T extends Record<keyof T, (s: string, payload: any) => void>>(callbackMap: T) {

  const result: Record<string, (...a: any[]) => any> = {}

  Object.keys(callbackMap).forEach(key => {
    if (typeof callbackMap[key as keyof T] === 'function') {
      result[key] = callbackMap[key as keyof T].bind(null, "data");
    }
  })

  return result as {
    [P in keyof T]: T[P] extends (s: string, ...p: infer P) => infer R ? (...p: P) => R : never;
  };
}

const maps = convert<IActions>({
  do1: (s: string, payload: number) => {
    //
  },
  do2(s: string, payload: string) {
    //
  }
});

maps.do1(1); // valid
maps.do1("1"); //err
maps.smth("1"); // should be type-check error, but TS thinks it's valid

Spielen