Les génériques fournissent des éléments de code réutilisables qui fonctionnent avec un certain nombre de types au lieu d'un seul. Les génériques permettent de traiter le type comme une variable et de le spécifier lors de l'utilisation, à l'instar des paramètres de fonction.
Les génériques peuvent être utilisés avec les fonctions (création d'une fonction générique), les classes (création d'une classe générique) et les interfaces (création d'une interface générique).
Utilisation de base
Vous avez probablement déjà utilisé des génériques sans le savoir - l'utilisation la plus courante des génériques est la déclaration d'un tableau :
const myArray : string[] ;
Ce n'est pas très spécial à première vue, nous déclarons simplement myArray
comme un tableau de chaînes de caractères, mais c'est la même chose qu'une déclaration générique :
const myArray : Tableau ;
Garder les choses explicites
Commençons par un exemple très simple : comment porter cette fonction JS vanille sur TypeScript ?
function getPrefiledArray(filler, length) {
return (new Array(length)).fill(filler) ;
}
Cette fonction renvoie un tableau contenant la quantité donnée de bouche-trou
Ainsi longueur
sera nombre
et la fonction entière renverra un tableau de bouche-trou
- mais qu'est-ce que le mastic ? À ce stade, il peut s'agir de n'importe quoi. tous
:
function getPrefiledArray(filler : any, length : number) : any[] {
return (new Array(length)).fill(filler) ;
}
Utilisation tous
est certainement générique - nous pouvons passer littéralement n'importe quoi, de sorte que "travailler avec un certain nombre de types au lieu d'un seul type" de la définition est entièrement couvert, mais nous perdons la connexion entre bouche-trou
et le type de retour. Dans ce cas, nous voulons renvoyer une chose commune, et nous pouvons explicitement définir cette chose commune comme étant paramètre de type:
function getPrefiledArray(filler : T, length : number) : T[] {
return (new Array(length)).fill(filler) ;
}
et l'utiliser comme suit :
const prefilledArray = getPrefiledArray(0, 10) ;
Contraintes génériques
Examinons d'autres cas, probablement plus courants. Pourquoi utilise-t-on des types dans les fonctions ? Pour moi, c'est pour m'assurer que les arguments passés à la fonction auront des propriétés avec lesquelles je veux interagir.
Une fois de plus, essayons de porter une simple fonction JS vanille sur TS.
function getLength(thing) {
return thing.length ;
}
Nous nous trouvons face à un problème non trivial : comment s'assurer que la chose possède une longueur
et la première idée peut être de faire quelque chose comme :
function getLength(thing : typeof Array):number {
return thing.length ;
}
et selon le contexte, cela peut être correct, mais globalement nous sommes un peu génériques - cela fonctionnera avec des tableaux de plusieurs types, mais que se passe-t-il si nous ne savons pas vraiment si la chose doit toujours être un tableau - peut-être que la chose est un terrain de football ou une peau de banane ? Dans ce cas, nous devons rassembler les propriétés communes de cette chose dans une construction qui peut définir les propriétés d'un objet - une interface :
interface IThingWithLength {
length : nombre ;
}
Nous pouvons utiliser IThingWithLength
comme type de l'interface chose
paramètre :
function getLength(thing : IThingWithLength):number {
return thing.length ;
}
franchement, dans cet exemple simple, cela conviendra parfaitement, mais si nous voulons garder ce type générique, et ne pas être confrontés au problème du premier exemple, nous pouvons utiliser Contraintes génériques:
function getLength(thing : T):number {
return thing.length ;
}
et l'utiliser :
interface IBananaPeel {
épaisseur : nombre ;
longueur : nombre ;
}
const bananaPeel : IBananaPeel = {épaisseur : 0,2, longueur : 3,14} ;
getLength(bananaPeel) ;
Utilisation s'étend
garantit que T
contiendra des propriétés définies par IThingWithLength
.
Classes génériques
Jusqu'à présent, nous avons travaillé avec des fonctions génériques, mais ce n'est pas le seul endroit où les génériques brillent, voyons comment nous pouvons les incorporer dans les classes.
Tout d'abord, essayons de stocker des bananes dans le panier à bananes :
class Banana {
constructeur(
public length : nombre,
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)) ;
Essayons maintenant de créer un panier à usage général, pour des objets différents ayant le même type :
class Basket {
private stuff : T[] = [] ;
add(thing : T) : void {
this.stuff.push(thing) ;
}
}
const bananaBasket = new Basket() ;
Enfin, supposons que notre panier soit un conteneur de matières radioactives et que nous ne puissions stocker que des matières qui ont Rayonnement ionisant
propriété :
interface IRadioactive {
ionizingRadiation : number ;
}
class RadioactiveContainer {
private stuff : T[] = [] ;
add(thing : T) : void {
this.stuff.push(thing) ;
}
}
Interface générique
Enfin, essayons de rassembler toutes nos connaissances et de construire un empire radioactif en utilisant également des interfaces génériques :
// Définir des attributs communs pour les conteneurs
interface IRadioactive {
ionizingRadiation : number ;
}
// Définit quelque chose de radioactif
interface IBanana extends IRadioactive {
length : nombre ;
color : string ;
}
// Définit quelque chose qui n'est pas radioactif
interface IDog {
weight : number ;
}
// Définir une interface pour un conteneur qui ne peut contenir que des éléments radioactifs
interface IRadioactiveContainer {
add(thing : T) : void ;
getRadioactiveness():number ;
}
// Définir une classe implémentant l'interface du conteneur radioactif
class RadioactiveContainer implements IRadioactiveContainer {
private stuff : T[] = [] ;
add(thing : T) : void {
this.stuff.push(thing) ;
}
getRadioactiveness() : number {
return this.stuff.reduce((a, b) => a + b.ionizingRadiation, 0)
}
}
// ERREUR ! Le type 'IDog' ne satisfait pas la contrainte 'IRadioactive'
// Et c'est un peu brutal de stocker des chiens dans un conteneur radioactif
const dogsContainer = new RadioactiveContainer() ;
// Tout va bien, fam !
const radioactiveContainer = new RadioactiveContainer() ;
// N'oubliez pas de trier vos déchets radioactifs - créez une poubelle séparée pour les bananes uniquement
const bananasContainer = new RadioactiveContainer() ;
C'est tout !