Generiske programmer gir gjenbrukbare kodebiter som fungerer med en rekke typer i stedet for én enkelt type. Generiske programmer gjør det mulig å behandle typen som en variabel og spesifisere den ved bruk, på samme måte som funksjonsparametere.
Generics kan brukes i forbindelse med funksjoner (generiske funksjoner), klasser (generiske klasser) og grensesnitt (generiske grensesnitt).
Grunnleggende bruk
Du har sannsynligvis brukt generics tidligere uten å vite det - den vanligste bruken av generics er å deklarere en matrise:
const myArray: string[];
Det er ikke så spesielt ved første øyekast, vi bare erklærer myArray
som en matrise med strenger, men det er det samme som en generisk deklarasjon:
const myArray: Array;
Holder ting eksplisitt
La oss starte med et veldig enkelt eksempel - hvordan kan vi portere denne vanilje-JS-funksjonen til TypeScript:
function getPrefiledArray(filler, length) {
return (new Array(length)).fill(filler);
}
Denne funksjonen returnerer en matrise fylt med en gitt mengde fyllstoff
, så lengde
vil være antall
og hele funksjonen vil returnere en matrise av fyllstoff
- men hva er fyllstoff? På dette tidspunktet kan det være hva som helst, så et alternativ er å bruke noen
:
function getPrefiledArray(filler: any, length: number): any[] {
return (new Array(length)).fill(filler);
}
Ved hjelp av noen
er absolutt generisk - vi kan sende bokstavelig talt hva som helst, så "arbeid med et antall typer i stedet for en enkelt type" fra definisjonen er fullt ut dekket, men vi mister forbindelsen mellom fyllstoff
typen og returtypen. I dette tilfellet ønsker vi å returnere en felles ting, og vi kan eksplisitt definere denne felles tingen som type parameter:
function getPrefiledArray(filler: T, length: number): T[] {
return (new Array(length)).fill(filler);
}
og bruke slik:
const prefilledArray = getPrefiledArray(0, 10);
Generiske begrensninger
La oss se på andre, sannsynligvis mer vanlige tilfeller. Hvorfor bruker vi egentlig typer i funksjoner? For meg er det for å sikre at argumentene som sendes til funksjonen, har noen egenskaper som jeg ønsker å samhandle med.
La oss nok en gang prøve å portere en enkel vanilje-JS-funksjon til TS.
function getLength(ting) {
return thing.length;
}
Vi har en ikke-triviell gåte - hvordan sikre at tingen har en lengde
eiendom, og første tanke kan være å gjøre noe sånt som:
function getLength(thing: typeof Array):number {
returnere ting.lengde;
}
og avhengig av konteksten kan det være riktig, men generelt sett er vi litt generiske - det vil fungere med matriser av flere typer, men hva om vi ikke vet om tingen alltid skal være en matrise - kanskje tingen er en fotballbane eller et bananskall? I dette tilfellet må vi samle de vanlige egenskapene til den tingen i en konstruksjon som kan definere egenskapene til et objekt - et grensesnitt:
interface IThingWithLength {
lengde: antall;
}
Vi kan bruke ITingWithLength
grensesnitt som type av ting
parameter:
function getLength(thing: IThingWithLength):number {
return thing.length;
}
i dette enkle eksempelet vil det være helt greit, men hvis vi ønsker å holde denne typen generisk, og ikke møte problemet fra det første eksempelet, kan vi bruke Generiske begrensninger:
function getLength(thing: T):number {
returnere ting.lengde;
}
og bruke den:
grensesnitt IBananaPeel {
tykkelse: number;
lengde: number;
}
const bananaPeel: IBananaPeel = {tykkelse: 0,2, lengde: 3,14};
getLength(bananaPeel);
Ved hjelp av strekker seg
sikrer at T
vil inneholde egenskaper som er definert av ITingWithLength
.
Generiske klasser
Frem til nå har vi jobbet med generiske funksjoner, men det er ikke det eneste stedet generiske funksjoner kan briljere, så la oss se hvordan vi kan innlemme dem i klasser.
La oss først prøve å lagre en haug med bananer i banankurven:
klasse Banana {
konstruktør(
offentlig lengde: tall,
offentlig farge: streng,
offentlig ioniserende stråling: tall
) {}
}
class BananaBasket {
private bananas: Banana[] = [];
add(banana: Banana): void {
this.bananas.push(banan);
}
}
const bananaBasket = new BananaBasket();
bananaBasket.add(new Banana(3.14, 'red', 10e-7));
La oss nå prøve å lage en kurv for generelle formål, til forskjellige ting med samme type:
class Kurv {
private stuff: T[] = [];
add(ting: T): void {
this.stuff.push(ting);
}
}
const bananaBasket = new Basket();
Og til slutt, la oss anta at kurven vår er en beholder for radioaktivt materiale, og at vi bare kan lagre materiale som har ioniserende stråling
eiendom:
grensesnitt IRadioaktiv {
ionizingRadiation: number;
}
class RadioactiveContainer {
private stuff: T[] = [];
add(ting: T): void {
this.stuff.push(ting);
}
}
Generisk grensesnitt
La oss til slutt prøve å samle all kunnskapen vår og bygge et radioaktivt imperium også ved hjelp av generiske grensesnitt:
// Definer felles attributter for containere
grensesnitt IRadioactive {
ionizingRadiation: number;
}
// Definerer noe som er radioaktivt
grensesnittet IBanana extends IRadioactive {
lengde: number;
farge: string;
}
// Definer noe som ikke er radioaktivt
grensesnitt IDog {
weight: number;
}
// Definerer grensesnittet for en beholder som bare kan inneholde radioaktive ting
interface IRadioactiveContainer {
add(ting: T): void;
getRadioactiveness():number;
}
// Definer klasse som implementerer grensesnittet for radioaktiv beholder
class RadioactiveContainer implementerer IRadioactiveContainer {
private stuff: T[] = [];
add(thing: T): void {
this.stuff.push(ting);
}
getRadioactiveness(): number {
return this.stuff.reduce((a, b) => a + b.ionizingRadiation, 0)
}
}
// FEIL! Type 'IDog' tilfredsstiller ikke begrensningen 'IRadioactive'
// Og det er ganske brutalt å lagre hunder i en radioaktiv beholder
const dogsContainer = new RadioactiveContainer();
// Alt i orden, fam!
const radioactiveContainer = new RadioactiveContainer();
// Husk å sortere det radioaktive avfallet - opprett en egen beholder kun for bananer
const bananasContainer = new RadioactiveContainer();
Det var alt, folkens!