-
Thomas Fritsch authored
suppression plugin-proposal-class-properties inutile désormais wording mise en forme synchro cours react
Thomas Fritsch authoredsuppression plugin-proposal-class-properties inutile désormais wording mise en forme synchro cours react
- D. POO avancée
- Sommaire
- D.1. Composition : La classe PizzaThumbnail
- D.2. Composition : La classe PizzaList
- D.3. Propriétés et méthodes statiques : La classe Router
- D.3.1 Rappels de syntaxe
- D.3.2. La classe Router
- D.4. Private, Setter & Getter : La propriété pizzaList.pizzas
- D.4.1. Rappels propriétés privées
- D.4.2. Rappels getters/setters
- D.4.3. Mise en oeuvre

D. POO avancée
Sommaire
- D.1. Composition : La classe PizzaThumbnail
- D.2. Composition : La classe PizzaList
- D.3. Propriétés et méthodes statiques : La classe Router
-
D.4. Private, Setter & Getter : La propriété
pizzaList.pizzas
D.1. Composition : La classe PizzaThumbnail
L'objectif de cet exercice est d'utiliser une classe à l'intérieur d'une autre. On va se servir de la classe Img
développée précédemment à l'intérieur d'un nouveau composant : PizzaThumbnail
.
-
Modifiez le code de la méthode
render()
de la classeComponent
pour lui permettre de recevoir dans le paramètrechildren
:- soit une chaîne de caractères (comme c'est déjà le cas actuellement)
- soit un tableau de chaînes de caractères.
Par exemple : sitagName
vaut"div"
et quechildren
vaut[ "youpi", "ça", "marche" ]
alorsrender()
retournera la chaîne"<div>youpiçamarche</div>"
.
NB : Pour ne pas alourdir trop le code de la méthode
render()
et pour avoir un code plus lisible, passez le code de rendu des enfants, dans une méthoderenderChildren()
.NB2 : Pour tester si
children
est un tableau (classeArray
), vous pouvez utiliser l'opérateurinstanceof
cf. https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Op%C3%A9rateurs/instanceofTestez votre classe avec le code suivant :
const title = new Component( 'h1', null, ['La', ' ', 'carte'] ); document.querySelector('.pageTitle').innerHTML = title.render();
-
Adaptez la méthode
render()
pour permettre de passer danschildren
non seulement des chaînes de caractères comme c'est déjà le cas mais aussi d'autresComponent
, comme ceci :const c = new Component( 'article', {name:'class', value:'pizzaThumbnail'}, [ new Img('https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300'), 'Regina' ] ); document.querySelector( '.pageContent' ).innerHTML = c.render();
Pour cela, la méthode
renderChildren()
devra maintenant tester le type de chaque enfant :- si cet enfant est lui-même une instance de Component, on fait alors appel à la méthode
render()
duComponent
enfant (petit indice : ça ressemble quand même beaucoup au concept de "récursivité"...) - si l'enfant est une chaîne de caractères, alors la chaîne est ajoutée telle qu'elle, comme auparavant
NB : Pour tester si un enfant est de la classe
Component
, vous pouvez là aussi utiliser l'opérateurinstanceof
Si votre code fonctionne correctement, vous devez avoir le rendu suivant :
NB : Si jamais le fond blanc derrière "Regina" n'apparaît pas, vérifiez que votre classe
Component
ajoute bien la classe CSSpizzaThumbnail
sur la balise<article>
... - si cet enfant est lui-même une instance de Component, on fait alors appel à la méthode
-
Grâce aux modifications que l'on vient de faire sur la classe
Component
, on peut maintenant créer la classePizzaThumbnail
(danssrc/components/PizzaThumbnail.js
).PizzaThumbnail
est en fait unComponent
auquel on peut passer un objet "pizza" (une cellule du tableaudata
) et qui est capable de générer le même code HTML que ce que l'on avait fait dans le TP1.Je vous fourni son code car l'intérêt ici, est simplement de s'assurer que votre classe
Component
gère bien les différents cas limites :export default class PizzaThumbnail extends Component { constructor({ name, image, price_small, price_large }) { super('article', { name: 'class', value: 'pizzaThumbnail' }, [ new Component('a', { name: 'href', value: image }, [ new Img(image), new Component('section', null, [ new Component('h4', null, name), new Component('ul', null, [ new Component('li', null, `Prix petit format : ${price_small.toFixed(2)} €`), new Component('li', null, `Prix grand format : ${price_large.toFixed(2)} €` ), ]), ]), ]), ]); } }
Comme vous le voyez, on utilise ici le même principe que pour la classe
Img
: on surcharge juste le constructeur en invoquant celui deComponent
(super(...)
) et en lui passant les bonstagName
,attribute
etchildren
.Attention : Je pense que vous l'aurez remarqué, le code que je vous ai fourni pour cette classe
PizzaThumbnail
n'est pas hyper pratique ni lisible : elle a une utilité dans notre exercice mais ce n'est pas un fonctionnement que je recommande dans la vraie vie ou dans vos futurs projets !Plutôt que d'imbriquer des
Component
dans desComponent
comme on le fait ici, on aurait pu par exemple overrider simplement la méthoderenderChildren()
pour lui faire retourner une template string contenant tout le code HTML.Dans le
src/main.js
, testez votre classe avec le code suivant :const pizza = data[0]; const pizzaThumbnail = new PizzaThumbnail(pizza); document.querySelector( '.pageContent' ).innerHTML = pizzaThumbnail.render();
Si votre classe
Component
est correctement codée (c'est à dire qu'elle prend en compte tous les cas limites) alors le code HTML généré sera le suivant :<article class="pizzaThumbnail"> <a href="https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300"> <img src="https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300" /> <section> <h4>Regina</h4> <ul> <li>Prix petit format : 6.50 €</li> <li>Prix grand format : 9.95 €</li> </ul> </section> </a> </article>
... et le rendu obtenu dans la page sera celui-ci :
D.2. Composition : La classe PizzaList
Après un exemple simple de composition, attaquons nous à un cas plus complexe : celui de la PizzaList
.
Cette classe va nous permettre d'afficher plusieurs vignettes côte à côte grâce au composant PizzaThumbnail
créé précédemment.
Créez la classe PizzaList
dans le fichier src/pages/PizzaList.js
:
- cette classe hérite de
Component
- instanciez-la dans
src/main.js
comme ceci :// `data` est le tableau défini dans `src/data.js` const pizzaList = new PizzaList(data); document.querySelector( '.pageContent' ).innerHTML = pizzaList.render();
- pour chaque cellule du tableau
data
, le composantPizzaList
créera un composantPizzaThumbnail
associé - le code HTML retourné par la méthode
pizzaList.render()
sera une balise<section class="pizzaList">
dans laquelle sera injectée la combinaison durender()
de chaquePizzaThumbnail
NB : en théorie, un simple override du constructor et l'utilisation de la méthode Array.map doivent suffire !
Le résultat attendu est le suivant :

D.3. Propriétés et méthodes statiques : La classe Router
Dans cet exercice, je vous propose de développer une classe Router
qui, à l'aide des propriétés et méthodes statiques, va gérer l'affichage à la fois du titre de la page, et de son contenu.
C'est une classe qui nous servira dans les prochains TP et qui nous permettra de naviguer d'une page à l'autre sans rechargement de page ! (principe de base des SPA)
D.3.1 Rappels de syntaxe
Pour rappel les propriétés et méthodes statiques peuvent se déclarer à l'aide du mot clé static
. Ces propriétés/méthodes n'existent qu'au niveau de la classe (et pas de l'instance) et s'utilisent comme ceci :
class Counter {
static counter = 0;
static getCounter() {
return this.counter++;
}
}
console.log(
Counter.getCounter(), // 0
Counter.counter, // 1
Counter.getCounter(), // 1
Counter.counter, // 2
);
Router
D.3.2. La classe -
Créez une classe
Router
(dans un modulesrc/Router.js
) avec :- une propriété statique
titleElement
- une propriété statique
contentElement
- une propriété statique
routes
- une propriété statique
-
Dans le
src/main.js
, renseignez les valeurs detitleElement
,contentElement
etroutes
comme ceci :Router.titleElement = document.querySelector('.pageTitle'); Router.contentElement = document.querySelector('.pageContent'); Router.routes = [ {path: '/', page: pizzaList, title: 'La carte'} ];
-
Enfin développez une méthode statique
Router.navigate(path)
qui permette d'afficher laPizzaList
et son titre comme ceci :Router.navigate('/'); // affiche 'La carte' dans .pageTitle // et la pizzaList dans .pageContent
pizzaList.pizzas
D.4. Private, Setter & Getter : La propriété Dans cet exercice je vous propose d'utiliser plusieurs syntaxes supplémentaires: les propriétés et méthodes privées ainsi que les getters/setters.
D.4.1. Rappels propriétés privées
Pour déclarer et utiliser des propriétés ou méthodes privées il suffit de les préfixer du caractère '#'
comme ceci :
class Character {
firstName;
#canCook = false; // propriété privée (#)
constructor(firstName) {
this.firstName = firstName;
this.#canCook = (firstName === 'Walter');
}
}
Comme les propriétés publiques, le support des propriétés et méthodes privées n'est pas encore dans la spec officielle mais devrait être intégré à ES2022 (ES13). Néanmoins il est possible de les utiliser grâce au preset-env
qui inclue ces syntaxes.
NB : Si vous vous demandez pourquoi on écrit
#propriete
et pasprivate propriete
comme dans d'autres langages, la réponse se trouve ici : https://github.com/tc39/proposal-class-fields/blob/master/PRIVATE_SYNTAX_FAQ.md#why-arent-declarations-private-x
D.4.2. Rappels getters/setters
Vous pouvez déclarer des getter et des setters de la manière suivante :
class Character {
#firstName;
set firstName(value) {
console.log(value);
this.#firstName = value.toLowerCase();
}
}
Ce sont en fait des méthodes qui se "déguisent" en propriétés. Pour utiliser le setter écrit au dessus, on peut faire simplement :
heisenberg.firstName = 'Walter';
On a l'impression d'utiliser une propriété, mais en réalité c'est une méthode qui est déclenchée, et donc le console.log(value)
va s'exécuter.
D.4.3. Mise en oeuvre
A l'aide des propriétés privées, des getters et des setters, faire en sorte que le code suivant permette d'afficher la liste des pizzas :
const pizzaList = new PizzaList([]);
Router.titleElement = document.querySelector('.pageTitle');
Router.contentElement = document.querySelector('.pageContent');
Router.routes = [{ path: '/', page: pizzaList, title: 'La carte' }];
Router.navigate('/'); // affiche une page vide
pizzaList.pizzas = data;
Router.navigate('/'); // affiche la liste des pizzas
C'est la fin de ce TP, rendez-vous très vite pour le prochain chapitre !