typescript - Typsäker högre ordningens funktion som imiterar gränssnittet för vilken funktion som helst som skickas in


Jag försöker skriva en högre ordningens funktion. Grundidén är att high får en funktion fn, och den returnerar en funktion som tar exakt samma parametrar och ger samma returtyp.

Det är en övning i att försöka förstå språket bättre. Jag har något som fungerar men raderar typen från inparametrarna. Vänligen se typerna Test1 vs Test2 nedan:

export function high<R>(fn: (...args: any[]) => R) {
  return (...args: any[]) => {
    const moddedArgs = args.map((el) =>
      typeof el === "string" ? el + "OMG" : el
    );

    return fn(...moddedArgs);
  };
}

const test1 = (nr1: number, str1?: string) => (str1 ?? "Wow").repeat(nr1);

test1(3, "yo");
// returns yoyoyo

test1(3);
// returns WowWowWow

const test2 = high(test1);

test2(3, "yo");
// returns yoOMGyoOMGyoOMG

type Test1 = typeof test1;
// type Test1 = (nr1: number, str1?: string) => string

type Test2 = typeof test2;
// type Test2 = (...args: any[]) => string

Lekplatslänk

Jag försökte ha ett P-typargument för parametrarna, vilket jag på något sätt fick att fungera. Men det hanterade inte valfria parametrar särskilt bra.

Några idéer om hur detta kan lösas?

Svar #1

I stället för att försöka gissa generiska typer för fn-funktionens parametrar och returtyper separat, kan vi ha en enda generisk typ F för hela funktionen, och sedan extrahera dess beståndsdelstyper med Parameters och ReturnType TypeScript inbyggda hjälp-typer:

export function high<F extends (...args: any[]) => any>(fn: F) {
  return (...args: Parameters<F>): ReturnType<F> => {
    const moddedArgs = args.map((el) =>
      typeof el === "string" ? el + "OMG" : el
    );

    return fn(...moddedArgs);
  };
}

const test2 = high(test1);
//    ^? (nr1: number, str1?: string | undefined) => string

Vi kan till och med gå lite längre, om det kan vara vettigt i ditt fall: genom att påstå att den modifierade funktionen har exakt samma typ som den ursprungliga funktionen (typ F ovan), får vi även dess tillhörande JSDoc!

function high2<F extends (...args: any[]) => any>(fn: F) {
  return high(fn) as F // If we assert the returned function type as exactly F, we get its associated JSDoc as well!
}

const test22 = high2(test1);
//    ^? (nr1: number, str1?: string) => string
// In the IDE, we should have any JSDoc associated to test1, popup as well on test22, since it is said to be exactly the same type.

Lekplatslänk

Svar #2

Du bör lagra hela typen av fn i en generisk typ. För att typa ...args kan vi använda Parameters<Fn>.

export function high<Fn extends (...args: any[]) => any>(fn: Fn) {
  return (...args: Parameters<Fn>) => {
    const moddedArgs = args.map((el) =>
      typeof el === "string" ? el + "OMG" : el
    );

    return fn(...moddedArgs);
  };
}

Lekplats