TypeScript-gränssnitts funktionsfält kontravarians typfel
Jag har följande typ, som fastställer att alla egenskaper kommer att vara funktioner, som antingen inte tar några argument, eller ett enda argument av typen Record<string, any>:
type FnTrait = Record<
string,
(input?: Record<string, any>) => any
>;
Jag försöker utöka denna typ till ett annat gränssnitt (som jag vill ha samma begränsning).
interface HasFn extends FnTrait {
someFn(): string; // no problem
anotherFn(o: {id: string}): number; // ts error 2411
}
Detta ger ett fel på anotherFn: Egenskapen 'anotherFn' av typen '(o: { id: string; }) => number' kan inte tilldelas strängindextypen '(input?: Record<string | number | symbol, any> | undefined) => any'.
Varför är det så att someFn inte ger något fel, medan anotherFn ger ts-fel 2411? Det verkar som att denna avsmalning borde tillåtas.
All hjälp skulle uppskattas mycket. Tack!
Svar #1
Detta är ett exempel på att funktionstyper är kontravariante i sina parametrar. Ordet "kontravariante" betyder "varierar på motsatt sätt". Om du gör en funktions parameter mer specifik (smal), gör du själva funktionstypen mer allmän (bred). Det innebär att istället för att göra HasFn till en subtyp av FnTrait (vilket extends betyder), gör du på sätt och vis att det blir en supertyp. Detta är inte tillåtet och bryter mot principen om utbytbarhet.
Specifikt är anotherFn() fel eftersom den kräver att dess argument har en string-värdesatt id-egenskap, medan FnTrait["anotherFn"] inte gör det. Det förväntas att du kan anropa vilken egenskap som helst av en FnTrait antingen utan parametrar eller med en enda parameter av nästan vilken typ som helst. Men en HasFn kan krascha om du anropar dess anotherFn()-metod utan parameter, eller med en parameter som saknar rätt typ av id-egenskap. Därför, såsom definierad, kan inte HasFn tilldelas till FnTrait, trots att den deklarerats för att utöka den:
const hasFn: HasFn = {
someFn: () => "",
anotherFn: o => o.id.length
}
const fnTrait: FnTrait = hasFn;
fnTrait.anotherFn({ a: 123 }); // okay at compile time, explodes at runtime
Eftersom anotherFn() innebär att du inte säkert kan ersätta ett FnTrait-värde där ett HasFn-värde efterfrågas, misslyckas FnTrait med att kunna tilldelas till HasFn, och du får ett fel.
Anledningen till att someFn() inte är fel är eftersom en funktion med färre parametrar kan tilldelas en funktion som tar fler parametrar. Detta beror på att someFn() nödvändigtvis kommer att ignorera alla parametrar som skickas till den, så det är säkert att behandla den som en funktion som möjligen kan ta emot en parameter:
fnTrait.someFn({ a: 123 }); // okay at compile time and runtime
Detta fungerar av samma anledning som anotherFn() misslyckas: utbytbarhet.
Playground-länk till kod
