Överbelastning av Typescript-funktion med generiska typer


Jag har några problem med att försöka skriva en liknande funktion som den i följande exempel

type Type = {
  x: string
}

function func<T extends Type>(v: T): boolean
function func<T extends Type>(v: T | null): boolean | undefined {
  return true
}

const getValue = (): Type | null => {
  return null
}
let x = getValue()
func(x)

men jag får följande meddelande eftersom det verkar som att kompilatorn försöker lösa generiken för den första funktionsdefinitionen

No overload matches this call.
  Overload 1 of 2, '(v: null): undefined', gave the following error.
    Argument of type 'Type | null' is not assignable to parameter of type 'null'.
      Type 'Type' is not assignable to type 'null'.
  Overload 2 of 2, '(v: Type): boolean', gave the following error.
    Argument of type 'Type | null' is not assignable to parameter of type 'Type'.
      Type 'null' is not assignable to type 'Type'.

Jag försökte skriva en enklare version av funktionen i den online TS-kompilatorn. Jag förväntar mig att kompilatorn ska säga till mig att det är en giltig funktion

Svar #1

Knepet med att överbelasta funktioner i TypeScript är den sista funktionsargumentdeklarationen. TypeScript ignorerar denna sista deklaration externt från funktionen och använder endast denna deklaration för typning inuti funktionssignaturen.

Den sista deklarationen måste uppfylla alla överlagringar, du kan se detta i Typescript-dokumentationen för funktionsöverlagringar. Det enklaste sättet att göra detta är helt enkelt att skicka alla parametrar och returvärde som any.

function func(v: number): number
function func(v: string): string
function func(v: any): any {
  return v;
}
func(false); // still throws type error - see note with error below

Externt kan du endast kalla denna funktion med ett nummer eller en sträng. Så om jag försöker kalla den med false så kastar den fortfarande ett typfel. Så detta bekräftar att den sista funktionsdeklarationen endast används för de interna typerna så länge de uppfyller alla överlagringar. Observera också att felet endast hänvisar till 2 överlagringar inte 3.

func(false);
     ^^^^^
No overload matches this call.
Overload 1 of 2, '(v: number): boolean', gave the following error.
  Argument of type 'boolean' is not assignable to parameter of type 'number'.
Overload 2 of 2, '(v: string): boolean | undefined', gave the following error.
  Argument of type 'boolean' is not assignable to parameter of type 'string'.(2769)

Men denna metod att skriva till any är oprecis och ger mycket lös typning inom funktionssignaturen. Ett bättre sätt är att unionera var och en av funktionens overload-parametrar och returvärdet. Så i detta enkla exempel skulle det se ut som...

function func(v: number): number
function func(v: string): string
function func(v: number | string): number | string {
  return v;
}
func(1) // no error
func('string') // no error
func(false) // throws type error

Så i ditt fall skulle du enkelt kunna använda den här koden...

type Type = {
  x: string
}

function func<T extends Type>(v: T): boolean
function func<T extends Type>(v: T | null): boolean | undefined
function func<T extends Type>(v: T | null): boolean | undefined {
  return true
}

const getValue = (): Type | null => {
  return null
}

let x = getValue()

func(x) // no more errors

Ja, detta duplicerar den andra överlagringsdeklarationen, detta är inte ett misstag, detta är nödvändigt! Att ta bort denna duplicerade deklaration gör att koden får fel som din ursprungliga kod. Se resonemanget för detta som diskuterats ovan relaterat till den sista funktionsdeklarationen.

Se arbetsdemo för TS-lekplats här

För din information - Det blir mycket svårare att uppfylla alla typer när du har flera generiska typer och/eller parametrar/returer som är olika för varje överlagringsdeklaration. Vid detta skede blir det att förlora typerna med any (alternativ 1) ett rimligt val.

Svar #2

Du bör ha en andra överlagring för null-parametertypen och en överlagring med samma signatur som din implementeringsfunktion för att acceptera en T|null-parameter.

function func(v:null):undefined
function func<T extends Type>(v:T):boolean
function func<T extends Type>(v:T|null):boolean|undefined
function func<T extends Type>(v:T|null):boolean|undefined{
  if (v === null) return undefined
  return true
}

Relaterade artiklar