Hvorfor ble `never`-typen introdusert i TypeScript?


Jeg har nå søkt på internett etter en forklaring på HVORFOR never-typen ble introdusert. Både TypeScript-håndboken og TypeScript Deep Dive forklarer hva den er, og noen andre artikler. Ingen av dem forklarer imidlertid HVORFOR du skulle bruke never-typen og hva den løser. De fleste eksemplene viser kode hvor kompilatoren allerede utleder never, så hvorfor må jeg tilordne typen? Hva løser det? Er det «bare» for å signalisere til intellisense hva jeg kan gjøre etterpå (dvs. ingenting - noe som kanskje er nyttig nok). Og hva bruker kompilatoren informasjonen til? Som alltid kan forklaringer på HVA og HVORDAN hjelpe folk å gjøre ting, men bare HVORFOR får dem til å forstå.

Svar #1

Type never er TypeScripts representasjon av bunn-typen fra typeteori. Så hvis du leter etter motivasjon for og bruksområder for never i TypeScript, kan det være lurt å se nærmere på bunn-typen i informatikk generelt.

Det kanoniske svaret på «hvorfor never ble introdusert i TypeScript» finnes sannsynligvis best ved å analysere kommentarene på pull-forespørselen som introduserte det og de to problemene det adresserer: en forespørsel om en bunn-type, og en forespørsel om å bedre håndtere uoppnåelige funksjonsreturer.

Hovedbruken for utviklere er å ha en type for funksjoner (eller deler av funksjoner) som ikke returnerer noen verdi i det hele tatt, ikke engang undefined. Sammenlign disse funksjonene:

function u(): undefined {
    return;
}
const uRet = u();
console.log(typeof uRet); // undefined

function v(): void { }
const vRet = v();
console.log(typeof vRet); // undefined

som returnerer undefined-verdier, med disse funksjonene:

function throws(): never {
    throw new Error();
}
const tRet = throws();
console.log(typeof tRet); // does not run

function loops(): never {
    while (true) { }
}
const lRet = loops();
console.log(typeof lRet); // does not run

som ikke returnerer verdier i det hele tatt, på grunn av en unntakstilstand eller en uendelig løkke. never-typen gjør det mulig for både utviklere og kompilatoren å resonnere om deler av kode som aldri kan kjøres.

Det finnes andre bruksområder for never, men jeg har ikke tenkt å prøve å liste dem opp, siden du allerede er kjent med kodeeksempler. Det er opplysende å undersøke TypeScript-standardbiblioteket for å finne stedene hvor never dukker opp.

Endelig, når du spør

De fleste eksemplene viser kode hvor kompilatoren allerede slutter seg til never, så hvorfor må jeg tilordne typen?

merk at selv i tilfeller der kompilatoren utleder never for deg, er det nyttig at det eksisterer i språket, på samme måte som string er nyttig selv når det utledes av kompilatoren. Dette virker som et annet spørsmål om når du kanskje vil eksplisitt annotere typer i stedet for å la kompilatoren utlede dem, og omvendt. Men dette innlegget er allerede langt, og dette er sannsynligvis besvart andre steder.

Håper det hjelper; lykke til!

Rediger: Det er mange grunner til at en programmerer vil bruke never, selv om mange programmerere kanskje aldri trenger å bruke det. La meg ramse opp noen grunner jeg kan tenke på:

  • Hvis du skriver typings for et JavaScript-bibliotek der en funksjon kaster et unntak i stedet for å returnere, kan du egentlig ikke gjøre det uten never.

  • Generiske typeparametere standardiseres til {} når de ikke kan utledes av kompilatoren. Det «feiler litt åpent», siden {} er kompatibel med (nesten) alle verdier. Det finnes situasjoner der du vil at det skal «feile lukket» og være kompatibelt med ingen verdier. Du kan bruke never som standardparameter.

  • Unionen av en hvilken som helst type T med never er bare T, og snittet av T med never er never. Disse reglene (blant andre) lar en utvikler bygge ganske sofistikerte typefunksjoner som ville vært vanskeligere eller umulige uten never. For eksempel, her er Diff<T,U>:

    type Diff<T extends string, U extends string> =
      ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
    

    Det er en typfunksjon som tar en union av strengliteraler T og en union av strengliteraler U, og returnerer en union av alle verdiene i T som ikke er tilstede i U.

    Du kan bruke det til, blant annet, å fjerne en egenskap fra en type:

    type Foo = { a: string, b: number, c: boolean };
    type FooWithoutB = Pick<Foo, Diff<keyof Foo, 'b'>>;
    // equivalent to { a: string, c: boolean }
    

Det er litt som tallet null i matematikk. Du trenger det ikke; mange kulturer klarte seg bra uten konseptet null. Men det er utrolig nyttig og gjør det enkelt å uttrykke ideer som ellers ville vært tungvint eller umulig.

Forhåpentligvis er det litt mer overbevisende? Uansett, lykke til igjen.

Relaterte artikler