typescript - En skickad funktion utan argument kastar inte ett fel
Jag använder TypeScript 4.3.5. I koden behöver jag skicka en funktion och jag behöver kontrollera om funktionen innehåller nödvändiga argument. Den här koden genererar inget syntaxfel:
function functionA (call: (name: string) => boolean) {
call("")
}
function functionB () {
return true;
}
function functionC() {
functionA(functionB); // this doesn't throw a syntax error
functionB(""); // this throws a syntax error
}
Jag försökte också den här typdefinitionen:
type FunctionB = {
(name: string): boolean
}
Och ingenting fungerar. Jag förväntar mig att TypeScript ska kasta ett syntaxfel när jag anropar functionA(functionB) eftersom functionB inte innehåller det förväntade argumentet. När jag anropar functionB("") kastar det ett fel.
Finns det någon lösning på hur man får ett syntaxfel när jag anropar functionA(functionB)?
Svar #1
Nej, detta är avsedd beteende och du kan inte tvinga TypeScript att kasta ett syntaxfel i ett sådant scenario.
Enligt TypeScripts FAQ:
Detta är det förväntade och önskade beteendet. Först, [...] [
functionB] är ett giltigt argument för [functionA] eftersom det säkert kan ignorera extra parametrar.För det andra, låt oss överväga ett annat fall:
let items = [1, 2, 3]; items.forEach(arg => console.log(arg));Detta är isomorfiskt med exemplet som "ville ha" ett fel. Vid körning anropar forEach den givna callbacken med tre argument (value, index, array), men oftast använder callbacken bara ett eller två av argumenten. Detta är ett mycket vanligt JavaScript-mönster och det skulle vara betungande att behöva uttryckligen deklarera oanvända parametrar.
Men forEach borde bara markera sina parametrar som valfria! t.ex.
forEach(callback: (element?: T, index?: number, array?: T[]))
Detta är inte vad en valfri återanropsparameter betyder. Funktionssignaturer läses alltid från anroparens perspektiv. Om forEach deklarerade att dess återanropsparametrar var valfria, betyder det att "forEach kan anropa återanropet med 0 argument".
Betydelsen av en valfri callback-parameter är denna:
// Invoke the provided function with 0 or 1 argument
function maybeCallWithArg(callback: (x?: number) => void) {
if (Math.random() > 0.5) {
callback();
} else {
callback(42);
}
}
forEach tillhandahåller alltid alla tre argument till sin callback. Du behöver inte kontrollera om indexargumentet är odefinierat - det är alltid där; det är inte valfritt.
Som förklarat i @Liad:s svar används detta mönster i många JavaScript-funktioner, både inbyggda och andra (tänk Array#forEach, Array#map, Array#reduce), och att kräva att användare av funktionerna deklarerar oanvända parametrar skulle vara "betungande" i bästa fall, som nämnts i FAQ-posten.
Därför kan functionB tilldelas till call så länge den har samma returtyp och inte lägger till några fler parametrar, men att deklarera färre parametrar kommer inte att orsaka ett fel.
När det gäller huruvida det är möjligt att tvinga TypeScript-kompilatorn att generera ett syntaxfel, tas detta också upp längst ner i den ovan nämnda FAQ-posten:
Det finns för närvarande inget sätt i TypeScript att ange att en callback-parameter måste finnas. Observera att denna typ av upprätthållande aldrig direkt skulle fixa ett fel. Med andra ord, i en hypotetisk värld där forEach-callbacks måste acceptera minst ett argument, skulle du ha denna kod:
[1, 2, 3].forEach(() => console.log("just counting")); // ~~ Error, not enough arguments?vilket skulle vara "fixat", men inte gjort mer korrekt, genom att lägga till en parameter:
[1, 2, 3].forEach(x => console.log("just counting")); // OK, but doesn't do anything different at all
Svar #2
Ts lekplats
Typescript villkorlig operator till undsättning... på ett sätt?
type FnStrongTypedOrNever<T extends (arg: any) => any, TConstraint extends any[]> =
[...Parameters<T>, ReturnType<T>] extends TConstraint ? T : never;
function functionA<T extends (arg: any) => any>(call: FnStrongTypedOrNever<T, [string, boolean]>) {
call("")
}
function functionB() {
return true;
}
function functionD(name: string) {
return true;
}
function functionC() {
functionA(functionD); // this doesn't throw a syntax error
functionA(functionB); // this throws a syntax error
functionB(""); // this throws a syntax error
}
En snabb och smutsig lösning som använder TypeScripts extends-syntax. Med lite extra typmagi.
Typen FnStrongTypedOrNever<T, TConstraint> returnerar never om funktionen inte uppfyller begränsningen.
Så, i slutet av dagen... felet du får är Argument of type '() => boolean' är inte tilldelningsbart till parameter av typ 'never'.
För tillfället är detta det bästa du kan göra med TS (såvitt jag vet)
REDIGERA:
Uppdaterad lösning
Den här har ett bättre felmeddelande via TConstraint & ''-användning istället för never
Nu kan du tydligt se att parameterlistan inte matchar.
Detta är fortfarande en typ av hack, och såvitt jag vet kan du inte ändra de omgivande typerna för att få detta att fungera globalt.
