Geneerilised koodid pakuvad korduvkasutatavaid koodijuppe, mis töötavad ühe tüübi asemel mitme tüübiga. Geneerikud pakuvad võimalust käsitleda tüüpi kui muutujat ja täpsustada seda kasutamisel, sarnaselt funktsiooni parameetritele.
Geneerikaid saab kasutada koos funktsioonidega (luues geneerilisi funktsioone), klassidega (luues geneerilisi klasse) ja liidestega (luues geneerilisi liideseid).
Põhiline kasutamine
Tõenäoliselt olete varem kasutanud geneerilisi elemente isegi ilma seda teadmata - kõige tavalisem geneeriliste elementide kasutamine on massiivi deklareerimine:
const myArray: string[];
See ei ole esmapilgul liiga eriline, me lihtsalt deklareerime myArray
kui stringide massiivi, kuid see on sama, mis üldine deklaratsioon:
const myArray: Array;
Asjade selgesõnalisus
Alustame väga lihtsa näitega - kuidas saaksime selle vanilla JS-funktsiooni portida TypeScript-le:
function getPrefiledArray(filler, length) {
return (new Array(length)).fill(filler);
}
See funktsioon tagastab massiivi, mis on täidetud antud hulga täitematerjal
, nii et pikkus
on number
ja kogu funktsioon tagastab massiivi täitematerjal
- kuid mis on täitematerjal? Siinkohal võib see olla mis tahes, nii et üks võimalus on kasutada mis tahes
:
function getPrefiledArray(filler: any, length: number): any[] {
return (new Array(length)).fill(filler);
}
Kasutades mis tahes
on kindlasti üldine - me võime edastada sõna otseses mõttes mida iganes, nii et "töötamine mitmete tüüpidega ühe tüübi asemel" definitsioonist on täielikult kaetud, kuid me kaotame seotuse vahel täitematerjal
tüüp ja tagastustüüp. Sellisel juhul tahame tagastada mingi ühise asja ja me võime selle ühise asja selgesõnaliselt defineerida kui tüübiparameeter:
function getPrefiledArray(filler: T, length: number): T[] {
return (new Array(length)).fill(filler);
}
ja kasuta seda niimoodi:
const prefilledArray = getPrefiledArray(0, 10);
Üldised piirangud
Vaatleme erinevaid, tõenäoliselt tavalisemaid juhtumeid. Miks me tegelikult kasutame tüüpe funktsioonides? Minu jaoks on see selleks, et tagada, et funktsioonile edastatud argumentidel oleksid mingid omadused, millega ma tahan suhelda.
Proovime veelkord portida lihtsat vanilla JS funktsiooni TS-i.
function getLength(thing) {
return thing.length;
}
Meil on mittetriviaalne mõistatus - kuidas tagada, et asi on pikkus
vara ja esimene mõte võib olla teha midagi sellist:
function getLength(thing: typeof Array):number {
return thing.length;
}
ja sõltuvalt kontekstist võib see olla õige, üldiselt oleme natuke geneerilised - see töötab mitut tüüpi massiividega, aga mis siis, kui me ei tea tegelikult, kas asi peaks alati olema massiivi - võib-olla on asi jalgpalliväljak või banaanikoore? Sellisel juhul peame koondama selle asja ühised omadused mingisse konstruktsiooni, mis suudab defineerida objekti omadusi - liidesesse:
liides IThingWithLength {
length: number;
}
Me võime kasutada IThingWithLength
liides tüübina asi
parameeter:
function getLength(thing: IThingWithLength):number {
return thing.length;
}
ausalt öeldes selles lihtsas näites on see täiesti hea, kuid kui me tahame hoida seda tüüpi üldisena ja mitte seista silmitsi esimese näite probleemiga, siis võime kasutada Üldised piirangud:
function getLength(thing: T):number {
return thing.length;
}
ja kasutada seda:
liides IBananaPeel {
thickness: number;
length: number;
}
const bananaPeel: IBananaPeel = {thickness: 0.2, length: 3.14};
getLength(bananaPeel);
Kasutades laiendab
tagab, et T
sisaldab omadusi, mis on määratletud IThingWithLength
.
Üldised klassid
Siiani töötasime geneeriliste funktsioonidega, kuid see ei ole ainus koht, kus geneerilised funktsioonid särama hakkavad, vaatame, kuidas saame neid klassidesse lisada.
Kõigepealt proovime banaanide korvi ladustada kobar banaane:
klass Banaan {
konstruktor(
public length: number,
public color: string,
public ionizingRadiation: number
) {}
}
class BananaBasket {
private bananas: Banana[] = [];
add(banana: Banana): void {
this.bananas.push(banana);
}
}
const bananaBasket = new BananaBasket();
bananaBasket.add(new Banana(3.14, 'red', 10e-7));
Nüüd proovime luua üldotstarbelise korvi, erinevatele asjadele sama tüüpi:
class Basket {
private stuff: T[] = [];
add(asi: T): void {
this.stuff.push(thing);
}
}
const bananaBasket = new Basket();
Ja lõpetuseks, oletame, et meie korv on radioaktiivse materjali konteiner ja me saame ladustada ainult sellist ainet, mis on ioniseeriv kiirgus
vara:
liides IRadioactive {
ionizingRadiation: number;
}
class RadioactiveContainer {
private stuff: T[] = [];
add(asi: T): void {
this.stuff.push(thing);
}
}
Üldine liides
Lõpuks proovime koguda kõik oma teadmised kokku ja ehitada radioaktiivne impeerium ka kasutades Generic Interfaces:
// Konteinerite ühiste atribuutide määratlemine
liides IRadioactive {
ionizingRadiation: number;
}
// Määratleme midagi, mis on radioaktiivne
interface IBanana extends IRadioactive {
length: number;
color: string;
}
// Defineeri midagi, mis ei ole radioaktiivne
liides IDog {
weight: number;
}
// Defineeri liides konteineri jaoks, mis saab hoida ainult radioaktiivset kraami.
interface IRadioactiveContainer {
add(thing: T): void;
getRadioaktiivsus():number;
}
// Define class implementing radioactive container interface
class RadioactiveContainer implements IRadioactiveContainer {
private stuff: T[] = [];
add(asi: T): void {
this.stuff.push(thing);
}
getRadioactiveness(): number {
return this.stuff.reduce((a, b) => a + b.ionizingRadiation, 0)
}
}
// ERROR! Tüüp 'IDog' ei vasta piirangule 'IRadioactive'.
// Ja see on kuidagi jõhker, et koeri radioaktiivse konteineri sees hoida.
const dogsContainer = new RadioactiveContainer();
// Kõik hea fam!
const radioactiveContainer = new RadioactiveContainer();
// Ärge unustage oma radioaktiivseid jäätmeid sorteerida - looge eraldi prügikast ainult banaanide jaoks.
const bananasContainer = new RadioactiveContainer();
See on kõik, inimesed!