javascript - TypeScript: Är det möjligt att definiera en variadisk funktion som accepterar olika typer av argument?
Jag vill skriva en generell funktion som accepterar ett variabelt antal argument som kan ha olika typer och returnerar en tuple baserat på dessa argument.
Här är ett exempel i JavaScript:
function evaluate (...fns) {
return fns.map(fn => fn())
}
evaluate(
() => 10
) // [ 10 ]
evaluate(
() => 10,
() => 'f',
() => null
) // [ 10, 'f', null ]
Och i TypeScript behöver jag på något sätt konvertera spridningsargumenttuplen till en resulterande:
function evaluate<T1, T2 ... Tn> (
...fns: [() => T1, () => T2 ... () => Tn]
): [T1, T2 ... Tn] {
return fns.map(fn => fn()) as [T1, T2 ... Tn]
}
evaluate(
() => 10
) // [ 10 ]: [number]
evaluate(
() => 10,
() => 'f',
() => null
) // [ 10, 'f', null ]: [number, string, null]
Jag har försökt med ett naivt tillvägagångssätt genom att skapa en överbelastning för alla rimliga längder av tuple:
function evaluate<T1> (
fn1: () => T1
): [T1]
function evaluate<T1, T2> (
fn1: () => T1,
fn2: () => T2
): [T1, T2]
function evaluate<T1, T2, T3> (
fn1: () => T1,
fn2: () => T2,
fn3: () => T3
): [T1, T2, T3]
function evaluate<T1, T2, T3> (
...fns: Array<(() => T1) | (() => T2) | (() => T3)>
): [T1] | [T1, T2] | [T1, T2, T3] {
return fns.map(fn => fn()) as [T1] | [T1, T2] | [T1, T2, T3]
}
Men det ser hemskt ut, skalar dåligt och orsakar problem med en mer komplex funktion kropp.
Finns det något sätt att detta skulle kunna göras dynamiskt? Tack!
Svar #1
Det enklaste sättet att implementera detta är att göra evaluate() generisk i dess arrayliknande returtyp T (avsett att vara en tuplet-typ), och sedan representera fns-restparametern som en mappad typ på T, med notering att mappade array-/tuplet-typer också är array-/tuplet-typer:
function evaluate<T extends any[]>(
...fns: { [I in keyof T]: () => T[I] }
) {
return fns.map(fn => fn()) as T;
}
Observera att typangesen as T är nödvändig eftersom kompilatorn inte kan se att fns.map(fn => fn()) kommer att ha effekten av att omvandla en array/tvärled av funktionstyper till array/tvärled av motsvarande returtyper. Se Mappa tuple-typad värde till ett annat tuple-typat värde utan typomvandlingar för mer information.
Eftersom {[I in keyof T]:() => T[I]} är en homomorf avbildad typ där vi avbildar direkt över keyof T (se Vad betyder "homomorf avbildad typ" för mer information), kan kompilatorn härleda T från den (länkad sida är föråldrad, men fortfarande korrekt och ingen ny sida finns 🤷♂️).
Låt oss se det i handling:
const x = evaluate(() => 10);
// const x: [number]
const y = evaluate(
() => 10,
() => 'f',
() => null
)
// const y: [number, string, null]
Ser bra ut. Kompilatorn ser att x är av typen [number] och y är av typen [number, string, null]. Den beter sig också rimligt i fall där du skickar in ett restargument av okänd ordning/längd:
const fs = [() => "a", () => 3];
// const fs: ((() => string) | (() => number))[]
const z = evaluate(...fs);
// const z: (string | number)[]
Här är fs av typen Array<(()=>string) | (()=>number)>, och därför är z av den analoga typen Array<string | number>.
Lekplatslänk till kod
