Varför kastar det inte ett fel när formen på parametern är olika från den på argumentet i TypeScript?


Här är koderna:

function a(fn: (a: string) => void) {
  fn("Hello, World");
} 
a(function b() {
  return 1
});

Genom att lägga till typannoteringen till callbacken för funktionen a betyder det att funktionen a måste ta emot en funktion som har ett argument av typen string och ett returvärde av typen void. Men när jag skickade funktionen b som inte kunde uppfylla villkoren för typannoteringen, kastade TS fortfarande inte ett kompileringsfel. Varför?

Dessutom, när jag ändrade värdet som skickades till fn från "Hello, world" (string) till 123 (number), så kastade det ett fel. I det här fallet fungerade typannoteringen.

Svar #1

Två saker här:

Varför kan funktioner med färre parametrar tilldelas funktioner som tar fler parametrar?

och

Varför kan funktioner som returnerar icke-void tilldelas funktioner som returnerar void?

De två länkarna ovan är från Typescript FAQ, och de bör ge dig klarhet om det nämnda beteendet. Slutsatsen är att detta är ett förväntat resultat av de följande två logiska slutsatserna:

  1. Funktionen som du skickar som callback bör få lov att INTE använda parametern a av strängtyp.
  2. Att returnera mer information när det förväntade är inget, det vill säga void, är okej.

Så detta kommer inte att fungera, eftersom funktionen b förväntar sig ett nummer medan a skulle skicka en sträng.

// Argument of type '(p: number) => number' is not assignable to parameter of type '(a: string) => void'.
//  Types of parameters 'p' and 'a' are incompatible.
//    Type 'string' is not assignable to type 'number
a(function b(p: number) {
  return 1
});

Men detta kommer att fungera eftersom b inte har någon användning av strängen som funktionen a kommer att skicka till den... och det borde vara okej.

a(function b() {
  return 1
});

Här är länken till TS Playground: https://tsplay.dev/WyObJN

function a(fn: (a: string) => void) {
  fn("Hello, World");
} 


a(function b() {
  return 1
});


// Argument of type '(p: number) => number' is not assignable to parameter of type '(a: string) => void'.
//  Types of parameters 'p' and 'a' are incompatible.
//    Type 'string' is not assignable to type 'number
a(function b(p: number) {
  return 1
});


function a1(fn: (a: string) => number) {
  fn("Hello, World");
} 

// Argument of type '(p: string) => string' is not assignable to parameter of type '(a: string) => number'.
//  Type 'string' is not assignable to type 'number'.(2345)
a1(function b(p: string) {
  return p;
});

Så, formmissanpassning i återanropsfunktionen fungerar endast om det återanrop som skickas är kompatibelt med återanropsparametern.

Svar #2

Tänk på detta enkla exempel:

type StringToVoid = (a: string) => void
type VoidToNumber = () => number

declare let a: StringToVoid
declare let b: VoidToNumber

a = b // ok
b = a // error

Det är nästan säkert att tilldela b till a, precis som i ditt exempel. Behandla b som en subtyp av a. Inget dåligt händer om :

a=b;
// a is b
const result = a('str') // void, but in fact number

Om du skickar ett argument till en funktion som inte förväntar sig några argument påverkar det inte din logik. Samma gäller returtyp. Funktionen a returnerar ingenting. Därför kan du inte använda den. Om du tilldelar b till a returnerar den ett värde men TS kommer inte att tillåta dig att göra något med det.

Eftersom det inte är säkert att tilldela a till b eftersom a förväntar sig ett argument som kan leda till ett körfel

b = a

b().toFixed(10) // runtime error