1. Introduction
Dernière mise à jour : 10/08/2021
Composants Web
Les composants Web sont un ensemble d'API de plate-forme Web qui vous permettent de créer des balises HTML personnalisées, réutilisables et encapsulées à utiliser dans les pages Web et les applications Web. Les composants et widgets personnalisés basés sur les normes des composants Web fonctionneront dans les navigateurs modernes et pourront être utilisés avec n'importe quelle bibliothèque ou framework JavaScript fonctionnant avec HTML.
Qu'est-ce que Lit ?
Lit est une bibliothèque simple permettant de créer des composants Web rapides et légers qui fonctionnent dans n'importe quel framework, ou sans framework du tout. Il vous permet de créer, entre autres, des applications, des systèmes de conception et des composants partageables.
Lit fournit des API pour simplifier les tâches courantes des composants Web, comme la gestion des propriétés, des attributs et du rendu.
Points abordés
- Qu'est-ce qu'un composant Web ?
- Concepts des composants Web
- Créer un composant Web
- Que sont lit-html et LitElement ?
- Ce que fait Lit en plus d'un composant Web
Objectifs de l'atelier
- Composant Web vanille "J'aime"/"Je n'aime pas"
- Composant Web basé sur Lit avec un pouce vers le haut / le bas
Prérequis
- Un navigateur moderne mis à jour (Chrome, Safari, Firefox, Chromium Edge). Les composants Web fonctionnent dans tous les navigateurs modernes. Des polyfills sont disponibles pour Microsoft Internet Explorer 11 et Microsoft Edge non basé sur Chromium.
- Connaissance des langages HTML, CSS et JavaScript, ainsi que des outils pour les développeurs Chrome
2. Se préparer et explorer le terrain de jeu
Accéder au code
Tout au long de cet atelier de programmation, vous trouverez des liens vers le terrain de jeu Lit, comme suit :
Le terrain de jeu est un bac à sable de code qui s'exécute complètement dans votre navigateur. Il peut compiler et exécuter des fichiers TypeScript et JavaScript, et aussi résoudre automatiquement les importations dans les modules de nœud. Par exemple :
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://unpkg.com/lit?module';
Vous pouvez suivre l'intégralité du tutoriel dans le terrain de jeu Lit, en utilisant ces points de contrôle comme points de départ. Si vous utilisez VS Code, vous pouvez vous servir de ces points de contrôle pour télécharger le code de départ de n'importe quelle étape ou pour vérifier votre travail.
Explorer l'UI du terrain de jeu Lit

La capture d'écran de l'UI du terrain de jeu Lit montre les sections que vous allez utiliser dans cet atelier de programmation.
- Sélecteur de fichier. Notez la présence du bouton Plus…
- Éditeur de fichier
- Aperçu du code
- Bouton d'actualisation
- Bouton Télécharger
Configuration de VS Code (niveau avancé)
Voici les avantages que présente cette configuration de VS Code :
- Validation du type de modèle
- Saisie semi-automatique du code du modèle (IntelliSense)
Si npm et VS Code (avec le plug-in lit) sont déjà installés et que vous savez comment utiliser cet environnement, vous pouvez simplement télécharger et lancer ces projets comme suit :
- Appuyez sur le bouton de téléchargement.
- Extrayez le contenu du fichier .tar dans un répertoire.
- Installez un serveur de développement capable de résoudre des spécificateurs de module nus (l'équipe Lit recommande @web/dev-server).
- Voici un exemple :
package.json
- Voici un exemple :
- Exécutez le serveur de développement et ouvrez votre navigateur (si vous utilisez
@web/dev-server, vous pouvez utilisernpx web-dev-server --node-resolve --watch --open).- Si vous utilisez l'exemple
package.json, utiliseznpm run serve.
- Si vous utilisez l'exemple
3. Définir un élément personnalisé
Éléments personnalisés
Les composants Web sont un ensemble de quatre API Web natives. Les voici :
- Modules ES
- Éléments personnalisés
- Shadow DOM
- Modèles HTML
Vous avez déjà utilisé la spécification des modules ES, qui vous permet de créer des modules JavaScript avec des importations et des exportations qui sont chargées dans la page avec <script type="module">.
Définir un élément personnalisé
La spécification Custom Elements permet aux utilisateurs de définir leurs propres éléments HTML à l'aide de JavaScript. Les noms doivent contenir un trait d'union (-) pour les différencier des éléments de navigateur natifs. Effacez le fichier index.js et définissez une classe d'élément personnalisé :
index.js
class RatingElement extends HTMLElement {}
customElements.define('rating-element', RatingElement);
Un élément personnalisé est défini en associant une classe qui étend HTMLElement à un nom de balise avec des traits d'union. L'appel à customElements.define indique au navigateur d'associer la classe RatingElement au nom de balise ‘rating-element'. Cela signifie que chaque élément de votre document portant le nom <rating-element> sera associé à cette classe.
Placez un <rating-element> dans le corps du document et voyez ce qui s'affiche.
index.html
<body>
<rating-element></rating-element>
</body>
Maintenant, en regardant le résultat, vous verrez que rien n'a été affiché. Ce comportement est normal, car vous n'avez pas indiqué au navigateur comment afficher <rating-element>. Pour vérifier que la définition de l'élément personnalisé a réussi, sélectionnez <rating-element> dans le sélecteur d'éléments des outils de développement Chrome, puis appelez la commande suivante dans la console :
$0.constructor
Le résultat devrait être le suivant :
class RatingElement extends HTMLElement {}
Cycle de vie des éléments personnalisés
Les éléments personnalisés sont fournis avec un ensemble de hooks de cycle de vie. Les voici :
constructorconnectedCallbackdisconnectedCallbackattributeChangedCallbackadoptedCallback
constructor est appelé lorsque l'élément est créé pour la première fois, par exemple en appelant document.createElement(‘rating-element') ou new RatingElement(). Le constructeur est un bon endroit pour configurer votre élément, mais il est généralement considéré comme une mauvaise pratique d'effectuer des manipulations DOM dans le constructeur pour des raisons de performances de "démarrage" des éléments.
connectedCallback est appelé lorsque l'élément personnalisé est associé au DOM. C'est généralement là que les manipulations initiales du DOM ont lieu.
disconnectedCallback est appelé après que l'élément personnalisé a été supprimé du DOM.
attributeChangedCallback(attrName, oldValue, newValue) est appelé lorsque l'un des attributs spécifiés par l'utilisateur est modifié.
Le adoptedCallback est appelé lorsque l'élément personnalisé est adopté à partir d'un autre documentFragment dans le document principal via adoptNode, comme dans HTMLTemplateElement.
Afficher le DOM
Revenez maintenant à l'élément personnalisé et associez-lui du DOM. Définissez le contenu de l'élément lorsqu'il est associé au DOM :
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
this.innerHTML = `
<style>
rating-element {
display: inline-flex;
align-items: center;
}
rating-element button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
`;
}
}
customElements.define('rating-element', RatingElement);
Dans constructor, vous stockez une propriété d'instance appelée rating sur l'élément. Dans connectedCallback, vous ajoutez des enfants DOM à <rating-element> pour afficher la note actuelle, ainsi que les boutons "J'aime" et "Je n'aime pas".
4. Shadow DOM
Pourquoi utiliser Shadow DOM ?
À l'étape précédente, vous avez remarqué que les sélecteurs de la balise de style que vous avez insérée sélectionnent tous les éléments de notation et tous les boutons de la page. Cela peut entraîner une fuite des styles hors de l'élément et la sélection d'autres nœuds que vous ne souhaitez peut-être pas styliser. De plus, d'autres styles en dehors de cet élément personnalisé peuvent styliser involontairement les nœuds à l'intérieur de votre élément personnalisé. Par exemple, essayez de placer une balise de style dans l'en-tête du document principal :
index.html
<!DOCTYPE html>
<html>
<head>
<script src="./index.js" type="module"></script>
<style>
span {
border: 1px solid red;
}
</style>
</head>
<body>
<rating-element></rating-element>
</body>
</html>
La sortie doit comporter un cadre rouge autour de l'étendue de la note. Il s'agit d'un cas trivial, mais l'absence d'encapsulation DOM peut entraîner des problèmes plus importants pour les applications plus complexes. C'est là que Shadow DOM entre en jeu.
Associer une racine fantôme
Associez une racine fantôme à l'élément et affichez le DOM à l'intérieur de cette racine :
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
`;
}
}
customElements.define('rating-element', RatingElement);
Lorsque vous actualisez la page, vous remarquez que les styles du document principal ne peuvent plus sélectionner les nœuds à l'intérieur de la racine fantôme.
Quelle procédure avez-vous suivie ? Dans connectedCallback, vous avez appelé this.attachShadow, qui associe une racine fantôme à un élément. Le mode open signifie que le contenu fantôme est inspectable et que la racine fantôme est également accessible via this.shadowRoot. Jetez également un œil au composant Web dans l'inspecteur Chrome :

Vous devriez maintenant voir une racine fantôme extensible contenant le contenu. Tout ce qui se trouve à l'intérieur de cette racine fantôme est appelé "Shadow DOM". Si vous sélectionnez l'élément de notation dans les outils de développement Chrome et appelez $0.children, vous remarquerez qu'il ne renvoie aucun enfant. En effet, le Shadow DOM n'est pas considéré comme faisant partie de la même arborescence DOM que les enfants directs, mais plutôt de la Shadow Tree.
Light DOM
Expérience : ajoutez un nœud en tant qu'enfant direct de <rating-element> :
index.html
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>
Actualisez la page. Vous verrez que ce nouveau nœud DOM dans le DOM léger de cet élément personnalisé n'apparaît pas sur la page. En effet, Shadow DOM dispose de fonctionnalités permettant de contrôler la manière dont les nœuds Light DOM sont projetés dans le Shadow DOM via les éléments <slot>.
5. Modèles HTML
Pourquoi utiliser des modèles ?
L'utilisation de innerHTML et de chaînes de modèles littéraux sans assainissement peut entraîner des problèmes de sécurité liés à l'injection de script. Par le passé, des méthodes ont inclus l'utilisation de DocumentFragment, mais celles-ci présentent également d'autres problèmes, tels que le chargement des images et l'exécution des scripts lorsque les modèles sont définis, ainsi que des obstacles à la réutilisabilité. C'est là qu'intervient l'élément <template>. Les modèles fournissent un DOM inerte, une méthode très performante pour cloner des nœuds et des modèles réutilisables.
Utiliser des modèles
Ensuite, faites passer le composant aux modèles HTML :
index.html
<body>
<template id="rating-element-template">
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating"></span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
</template>
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>
</body>
Ici, vous avez déplacé le contenu du DOM dans une balise de modèle dans le DOM du document principal. Refactorisez maintenant la définition de l'élément personnalisé :
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
const templateContent = document.getElementById('rating-element-template').content;
const clonedContent = templateContent.cloneNode(true);
shadowRoot.appendChild(clonedContent);
this.shadowRoot.querySelector('.rating').innerText = this.rating;
}
}
customElements.define('rating-element', RatingElement);
Pour utiliser cet élément de modèle, vous interrogez le modèle, obtenez son contenu et clonez ces nœuds avec templateContent.cloneNode, où l'argument true effectue un clonage en profondeur. Vous initialisez ensuite le DOM avec les données.
Félicitations, vous avez maintenant un composant Web ! Malheureusement, il ne fait rien pour le moment. Ajoutez donc une fonctionnalité.
6. Ajouter des fonctionnalités
Liaisons de propriétés
Actuellement, la seule façon de définir la note sur l'élément de notation est de construire l'élément, de définir la propriété rating sur l'objet, puis de le placer sur la page. Malheureusement, ce n'est pas ainsi que les éléments HTML natifs fonctionnent généralement. Les éléments HTML natifs ont tendance à se mettre à jour lorsque les propriétés et les attributs changent.
Faites en sorte que l'élément personnalisé mette à jour la vue lorsque la propriété rating change en ajoutant les lignes suivantes :
index.js
constructor() {
super();
this._rating = 0;
}
set rating(value) {
this._rating = value;
if (!this.shadowRoot) {
return;
}
const ratingEl = this.shadowRoot.querySelector('.rating');
if (ratingEl) {
ratingEl.innerText = this._rating;
}
}
get rating() {
return this._rating;
}
Vous ajoutez un setter et un getter pour la propriété de note, puis vous mettez à jour le texte de l'élément de note s'il est disponible. Cela signifie que si vous définissez la propriété de notation sur l'élément, la vue sera mise à jour. Faites un test rapide dans la console des outils de développement.
Liaisons d'attributs
Maintenant, mettez à jour la vue lorsque l'attribut change. Cela ressemble à une entrée qui met à jour sa vue lorsque vous définissez <input value="newValue">. Heureusement, le cycle de vie des composants Web inclut attributeChangedCallback. Mettez à jour la note en ajoutant les lignes suivantes :
index.js
static get observedAttributes() {
return ['rating'];
}
attributeChangedCallback(attributeName, oldValue, newValue) {
if (attributeName === 'rating') {
const newRating = Number(newValue);
this.rating = newRating;
}
}
Pour que attributeChangedCallback se déclenche, vous devez définir un getter statique pour RatingElement.observedAttributes which defines the attributes to be observed for changes. Vous définissez ensuite la note de manière déclarative dans le DOM. Essayez cette commande :
index.html
<rating-element rating="5"></rating-element>
La note devrait maintenant être mise à jour de manière déclarative.
Fonctionnalité des boutons
Il ne manque plus que la fonctionnalité du bouton. Le comportement de ce composant doit permettre à l'utilisateur de voter pour ou contre un élément et de lui fournir un retour visuel. Vous pouvez implémenter cela avec des écouteurs d'événements et une propriété de réflexion, mais commencez par mettre à jour les styles pour fournir un retour visuel en ajoutant les lignes suivantes :
index.html
<style>
...
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
</style>
Dans Shadow DOM, le sélecteur :host fait référence au nœud ou à l'élément personnalisé auquel la racine fantôme est associée. Dans ce cas, si l'attribut vote est défini sur "up", le bouton "J'aime" devient vert. En revanche, si vote est défini sur "down", then it will turn the thumb-down button red. Implémentez maintenant la logique en créant une propriété / un attribut de réflexion pour vote, comme vous l'avez fait pour rating. Commencez par le setter et le getter de la propriété :
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
}
set vote(newValue) {
const oldValue = this._vote;
if (newValue === oldValue) {
return;
}
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
this._vote = newValue;
this.setAttribute('vote', newValue);
}
get vote() {
return this._vote;
}
Vous initialisez la propriété d'instance _vote avec null dans constructor, et dans le setter, vous vérifiez si la nouvelle valeur est différente. Si c'est le cas, ajustez la note en conséquence et, surtout, renvoyez l'attribut vote à l'hôte avec this.setAttribute.
Ensuite, configurez la liaison d'attribut :
index.js
static get observedAttributes() {
return ['rating', 'vote'];
}
attributeChangedCallback(attributeName, oldValue, newValue) {
if (attributeName === 'rating') {
const newRating = Number(newValue);
this.rating = newRating;
} else if (attributeName === 'vote') {
this.vote = newValue;
}
}
Il s'agit du même processus que celui que vous avez suivi avec la liaison d'attribut rating. Vous ajoutez vote à observedAttributes et vous définissez la propriété vote dans attributeChangedCallback. Enfin, ajoutez des écouteurs d'événements de clic pour donner une fonctionnalité aux boutons.
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
this._boundOnUpClick = this._onUpClick.bind(this);
this._boundOnDownClick = this._onDownClick.bind(this);
}
connectedCallback() {
...
this.shadowRoot.querySelector('.thumb_up')
.addEventListener('click', this._boundOnUpClick);
this.shadowRoot.querySelector('.thumb_down')
.addEventListener('click', this._boundOnDownClick);
}
disconnectedCallback() {
this.shadowRoot.querySelector('.thumb_up')
.removeEventListener('click', this._boundOnUpClick);
this.shadowRoot.querySelector('.thumb_down')
.removeEventListener('click', this._boundOnDownClick);
}
_onUpClick() {
this.vote = 'up';
}
_onDownClick() {
this.vote = 'down';
}
Dans constructor, vous liez des écouteurs de clics à l'élément et conservez les références. Dans connectedCallback, vous écoutez les événements de clic sur les boutons. Dans disconnectedCallback, vous nettoyez ces écouteurs et définissez vote de manière appropriée sur les écouteurs de clics eux-mêmes.
Félicitations, vous disposez désormais d'un composant Web complet. Essayez de cliquer sur certains boutons. Le problème est que mon fichier JS atteint maintenant 96 lignes, mon fichier HTML 43 lignes, et le code est assez verbeux et impératif pour un composant aussi simple. C'est là que le projet Lit de Google entre en jeu.
7. Lit-html
Point de contrôle du code
Pourquoi lit-html ?
Tout d'abord, la balise <template> est utile et performante, mais elle n'est pas associée à la logique du composant, ce qui rend difficile la distribution du modèle avec le reste de la logique. De plus, la façon dont les éléments de modèle sont utilisés se prête intrinsèquement au code impératif, ce qui, dans de nombreux cas, conduit à un code moins lisible que les modèles de programmation déclaratifs.
C'est là que lit-html entre en jeu. Lit html est le système de rendu de Lit qui vous permet d'écrire des modèles HTML en JavaScript, puis de les afficher et de les réafficher efficacement avec des données pour créer et mettre à jour le DOM. Il est semblable aux bibliothèques JSX et VDOM populaires, mais il s'exécute de manière native dans le navigateur et beaucoup plus efficacement dans de nombreux cas.
Utiliser Lit HTML
Ensuite, migrez le rating-element de composant Web natif pour utiliser le modèle Lit qui utilise les littéraux de modèles balisés, qui sont des fonctions qui prennent des chaînes de modèle comme arguments avec une syntaxe spéciale. Lit utilise ensuite des éléments de modèle en arrière-plan pour fournir un rendu rapide et des fonctionnalités de désinfection pour la sécurité. Commencez par migrer le <template> dans index.html vers un modèle Lit en ajoutant une méthode render() au composant Web :
index.js
// Dont forget to import from Lit!
import {render, html} from 'lit';
class RatingElement extends HTMLElement {
...
render() {
if (!this.shadowRoot) {
return;
}
const template = html`
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
</style>
<button class="thumb_down">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>`;
render(template, this.shadowRoot);
}
}
Vous pouvez également supprimer votre modèle depuis index.html. Dans cette méthode de rendu, vous définissez une variable appelée template et appelez la fonction littérale de modèle balisé html. Vous remarquerez également que vous avez effectué une liaison de données simple dans l'élément span.rating en utilisant la syntaxe d'interpolation littérale du modèle ${...}. Cela signifie que vous n'aurez plus besoin de mettre à jour impérativement ce nœud. De plus, vous appelez la méthode render de lit, qui affiche le modèle de manière synchrone dans la racine fantôme.
Migrer vers la syntaxe déclarative
Maintenant que vous vous êtes débarrassé de l'élément <template>, refactorisez le code pour appeler à la place la méthode render nouvellement définie. Vous pouvez commencer par utiliser la liaison d'écouteur d'événements de Lit pour clarifier le code de l'écouteur :
index.js
<button
class="thumb_down"
@click=${() => {this.vote = 'down'}}>
...
<button
class="thumb_up"
@click=${() => {this.vote = 'up'}}>
Les modèles Lit peuvent ajouter un écouteur d'événements à un nœud avec la syntaxe de liaison @EVENT_NAME. Dans ce cas, vous mettez à jour la propriété vote chaque fois que l'utilisateur clique sur ces boutons.
Ensuite, nettoyez le code d'initialisation de l'écouteur d'événements dans constructor, connectedCallback et disconnectedCallback :
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
}
connectedCallback() {
this.attachShadow({mode: 'open'});
this.render();
}
// remove disonnectedCallback and _onUpClick and _onDownClick
Vous avez pu supprimer la logique d'écouteur de clic des trois rappels et même supprimer complètement disconnectedCallback. Vous avez également pu supprimer tout le code d'initialisation du DOM de connectedCallback, ce qui le rend beaucoup plus élégant. Cela signifie également que vous pouvez vous débarrasser des méthodes d'écouteur _onUpClick et _onDownClick.
Enfin, mettez à jour les setters de propriété pour utiliser la méthode render afin que le DOM puisse se mettre à jour lorsque les propriétés ou les attributs changent :
index.js
set rating(value) {
this._rating = value;
this.render();
}
...
set vote(newValue) {
const oldValue = this._vote;
if (newValue === oldValue) {
return;
}
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
this._vote = newValue;
this.setAttribute('vote', newValue);
// add render method
this.render();
}
Ici, vous avez pu supprimer la logique de mise à jour du DOM du setter rating et ajouter un appel à render à partir du setter vote. Le modèle est désormais beaucoup plus lisible, car vous pouvez voir où les liaisons et les écouteurs d'événements sont appliqués.
Actualisez la page. Vous devriez voir un bouton d'évaluation fonctionnel qui devrait ressembler à ceci lorsque l'utilisateur appuie sur le bouton "J'aime" :

8. LitElement
Pourquoi LitElement ?
Certains problèmes sont toujours présents dans le code. Tout d'abord, si vous modifiez la propriété ou l'attribut vote, cela peut modifier la propriété rating, ce qui entraînera l'appel de render deux fois. Bien que les appels répétés de render soient essentiellement des no-op et efficaces, la VM JavaScript passe toujours du temps à appeler cette fonction deux fois de manière synchrone. Deuxièmement, l'ajout de nouvelles propriétés et de nouveaux attributs est fastidieux, car il nécessite beaucoup de code récurrent. C'est là que LitElement entre en jeu.
LitElement est la classe de base de Lit pour créer des composants Web rapides et légers qui peuvent être utilisés dans différents frameworks et environnements. Ensuite, voyons ce que LitElement peut faire pour nous dans rating-element en modifiant l'implémentation pour l'utiliser.
Utiliser LitElement
Commencez par importer et sous-classer la classe de base LitElement à partir du package lit :
index.js
import {LitElement, html, css} from 'lit';
class RatingElement extends LitElement {
// remove connectedCallback()
...
Vous importez LitElement, qui est la nouvelle classe de base pour rating-element. Ensuite, vous conservez votre importation html et enfin css, ce qui nous permet de définir des littéraux de modèle tagués CSS pour les mathématiques CSS, les modèles et d'autres fonctionnalités en coulisses.
Ensuite, déplacez les styles de la méthode de rendu vers la feuille de style statique de Lit :
index.js
class RatingElement extends LitElement {
static get styles() {
return css`
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
`;
}
...
C'est là que se trouvent la plupart des styles dans Lit. Lit reprendra ces styles et utilisera des fonctionnalités du navigateur telles que les feuilles de style constructibles pour accélérer le rendu et les transmettra au polyfill Web Components sur les anciens navigateurs si nécessaire.
Cycle de vie
Lit introduit un ensemble de méthodes de rappel du cycle de vie de rendu en plus des rappels natifs des composants Web. Ces rappels sont déclenchés lorsque les propriétés Lit déclarées sont modifiées.
Pour utiliser cette fonctionnalité, vous devez déclarer de manière statique les propriétés qui déclencheront le cycle de vie du rendu.
index.js
static get properties() {
return {
rating: {
type: Number,
},
vote: {
type: String,
reflect: true,
}
};
}
// remove observedAttributes() and attributeChangedCallback()
// remove set rating() get rating()
Ici, vous définissez que rating et vote déclencheront le cycle de vie du rendu LitElement, tout en définissant les types qui seront utilisés pour convertir les attributs de chaîne en propriétés.
<user-profile .name=${this.user.name} .age=${this.user.age}>
${this.user.family.map(member => html`
<family-member
.name=${member.name}
.relation=${member.relation}>
</family-member>`)}
</user-profile>
De plus, l'indicateur reflect de la propriété vote mettra automatiquement à jour l'attribut vote de l'élément hôte que vous avez déclenché manuellement dans le setter vote.
Maintenant que vous avez le bloc de propriétés statiques, vous pouvez supprimer toute la logique de mise à jour du rendu des attributs et des propriétés. Vous pouvez donc supprimer les méthodes suivantes :
connectedCallbackobservedAttributesattributeChangedCallbackrating(setters et getters)vote(setters et getters, mais en conservant la logique de modification du setter)
Vous conservez constructor et ajoutez une nouvelle méthode de cycle de vie willUpdate :
index.js
constructor() {
super();
this.rating = 0;
this.vote = null;
}
willUpdate(changedProps) {
if (changedProps.has('vote')) {
const newValue = this.vote;
const oldValue = changedProps.get('vote');
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
}
}
// remove set vote() and get vote()
Ici, vous initialisez simplement rating et vote, et vous déplacez la logique du setter vote vers la méthode de cycle de vie willUpdate. La méthode willUpdate est appelée avant render chaque fois qu'une propriété de mise à jour est modifiée, car LitElement regroupe les modifications de propriétés et rend le rendu asynchrone. Les modifications apportées aux propriétés réactives (comme this.rating) dans willUpdate ne déclenchent pas d'appels de cycle de vie render inutiles.
Enfin, render est une méthode de cycle de vie LitElement qui nous oblige à renvoyer un modèle Lit :
index.js
render() {
return html`
<button
class="thumb_down"
@click=${() => {this.vote = 'down'}}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button
class="thumb_up"
@click=${() => {this.vote = 'up'}}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>`;
}
Vous n'avez plus besoin de vérifier la racine fantôme ni d'appeler la fonction render précédemment importée à partir du package 'lit'.
Votre élément devrait maintenant s'afficher dans l'aperçu. Cliquez dessus.
9. Félicitations
Félicitations, vous avez créé un composant Web à partir de zéro et l'avez transformé en LitElement !
Lit est super petit (< 5 ko minifié et compressé au format .gz), super rapide et très agréable à utiliser pour coder ! Vous pouvez créer des composants à utiliser par d'autres frameworks ou créer des applications complètes avec.
Vous savez maintenant ce qu'est un composant Web, comment en créer un et comment Lit facilite leur création.
Point de contrôle du code
Voulez-vous comparer votre code final au nôtre ? Cliquez ici pour comparer les éditions.
Et ensuite ?
Découvrez d'autres ateliers de programmation.
- Lit pour les développeurs React
- Créer un lecteur de briques avec LitElement
- Créer un composant Stories avec LitElement
Complément d'informations
- Tutoriel interactif Lit
- Documentation Lit
- Open Web Components : communauté qui fournit des conseils et des outils
- WebComponents.dev : créez un composant Web dans tous les frameworks connus.
Communauté
- Lit and Friends Slack : la plus grande communauté de composants Web
- @buildWithLit sur Twitter : compte Twitter de l'équipe qui a créé Lit
- Web Components SF : un meetup sur les composants Web à San Francisco