Du composant Web à l'élément Lit

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 des pages et des applications Web. Les widgets et les composants personnalisés conçus selon les normes Web Component sont compatibles avec les navigateurs récents et peuvent être utilisés avec n'importe quel framework ou bibliothèque JavaScript compatible avec HTML.

Qu'est-ce que Lit ?

Lit est une bibliothèque simple qui permet de créer des composants Web légers et rapides qui fonctionnent avec n'importe quel framework, ou sans framework. Avec Lit, vous pouvez créer des composants, des applications, des systèmes de conception et plus encore 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 liés aux composants Web
  • Comment créer un composant Web
  • Que sont lit-html et LitElement ?
  • Ce que Lit fait sur un composant Web

Objectifs de l'atelier

  • Un composant Web vanilla "J'aime" ou "Je n'aime pas"
  • Composant Web "J'aime" ou "Je n'aime pas" basé sur Lit

Prérequis

  • Navigateurs récents et à jour (Chrome, Safari, Firefox, Chromium Edge) Les composants Web sont compatibles avec tous les navigateurs récents, et les polyfills sont disponibles pour Microsoft Internet Explorer 11 et Microsoft Edge sans chrome.
  • Connaissance des langages HTML, CSS et JavaScript, ainsi que des outils pour les développeurs Chrome

2. Configuration et Exploration de Playground

Accéder au code

Tout au long de l'atelier de programmation, vous trouverez des liens vers le terrain de jeu Lit comme suit:

Google Playground est un bac à sable de code qui s'exécute entièrement dans votre navigateur. Il peut compiler et exécuter des fichiers TypeScript et JavaScript, et résoudre automatiquement les importations sur les modules de nœud. Ex. :

// 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 utiliser ces points de contrôle pour télécharger le code de départ de n'importe quelle étape et pour vérifier votre travail.

Explorer l'interface utilisateur de Lit Playground

La barre d'onglets du sélecteur de fichiers est intitulée Section 1, la section d'édition du code est intitulée Section 2, l'aperçu du résultat est intitulée Section 3 et le bouton d'actualisation de l'aperçu à la section 4.

La capture d'écran de l'UI du terrain de jeu Lit met en évidence les sections que vous utiliserez dans cet atelier de programmation.

  1. Sélecteur de fichier Notez le bouton Plus...
  2. Éditeur de fichier.
  3. Aperçu du code
  4. Bouton d'actualisation
  5. Bouton Télécharger

Configuration de VS Code (avancé)

Voici les avantages de cette configuration VS Code:

  • Vérification du type de modèle
  • Modèle Intellisense & saisie semi-automatique

Si NPM et VS Code (avec le plug-in lit-plugin) sont déjà installés et que vous savez comment utiliser cet environnement, il vous suffit de télécharger et de démarrer ces projets en procédant comme suit:

  • Appuyez sur le bouton de téléchargement
  • Extraire le contenu du fichier tar dans un répertoire
  • Installez un serveur de développement capable de résoudre les spécificateurs de module nus (l'équipe Lit recommande @web/dev-server).
  • Exécutez le serveur de développement et ouvrez votre navigateur (si vous utilisez @web/dev-server, vous pouvez utiliser npx web-dev-server --node-resolve --watch --open).
    • Si vous utilisez l'exemple package.json, utilisez npm run serve.

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 des éléments personnalisés 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 du navigateur natif. 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 composé. L'appel de customElements.define indique au navigateur d'associer la classe RatingElement au tagName ‘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 observez le rendu.

index.html

<body>
 <rating-element></rating-element>
</body>

En examinant le résultat, vous constaterez que rien ne s'est affiché. Ce comportement est normal, car vous n'avez pas indiqué au navigateur comment afficher <rating-element>. Pour vérifier que l'élément personnalisé a bien été défini, sélectionnez <rating-element> dans les outils pour les développeurs Chrome. sélecteur d'éléments et, dans la console, en appelant:

$0.constructor

Vous devriez obtenir le résultat 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 :

  • constructor
  • connectedCallback
  • disconnectedCallback
  • attributeChangedCallback
  • adoptedCallback

constructor est appelé lors de la création initiale de l'élément, 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 le "démarrage" de l'élément des raisons de performances.

connectedCallback est appelé lorsque l'élément personnalisé est associé au DOM. C'est généralement à ce niveau que les manipulations DOM initiales se produisent.

disconnectedCallback est appelé une fois l'élément personnalisé supprimé du DOM.

attributeChangedCallback(attrName, oldValue, newValue) est appelé lorsque l'un des attributs spécifiés par l'utilisateur est modifié.

adoptedCallback est appelé lorsque l'élément personnalisé est adopté à partir d'un autre élément documentFragment dans le document principal via adoptNode, comme dans HTMLTemplateElement.

DOM de rendu

Revenez maintenant à l'élément personnalisé et associez-y un élément 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 les enfants DOM à <rating-element> pour afficher l'évaluation 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 du tag de style que vous avez inséré sélectionnent n'importe quel élément d'évaluation sur la page ainsi qu'un bouton. Cela peut entraîner une fuite des styles hors de l'élément et la sélection d'autres nœuds que vous n'avez peut-être pas l'intention de styliser. De plus, les autres styles en dehors de cet élément personnalisé peuvent appliquer un style involontaire aux nœuds à l'intérieur de votre élément personnalisé. Par exemple, essayez de placer un tag 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>

Le résultat doit être entouré d'une bordure rouge pour la note. Ce cas de figure est simple, mais l'absence d'encapsulation du 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 d'ombre à 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);

En actualisant la page, vous remarquerez que les styles du document principal ne peuvent plus sélectionner les nœuds à l'intérieur de la racine de l'ombre.

Quelle procédure avez-vous suivie ? Dans connectedCallback, vous avez appelé this.attachShadow, ce qui associe une racine fantôme à un élément. Le mode open signifie que le contenu de l'ombre peut être inspecté et rend la racine fantôme accessible via this.shadowRoot également. Examinez également le composant Web dans l'inspecteur Chrome:

Arborescence dom dans l&#39;inspecteur Chrome. Un élément <rating-element> avec a#shadow-root (open) comme enfant et le DOM placé précédemment dans cette shadowroot.

Vous devriez maintenant voir une racine fantôme extensible qui contient 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 d'évaluation dans les outils pour les développeurs 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 en tant qu'élément enfant direct, mais plutôt en tant qu'arborescence des ombres.

DOM léger

Un test: 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>

Si vous actualisez la page, le nouveau nœud DOM du Light DOM de cet élément personnalisé n'apparaît pas. En effet, le 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 des éléments <slot>.

5. Modèles HTML

Pourquoi utiliser des modèles ?

L'utilisation de innerHTML et de chaînes littérales de modèle sans nettoyage peut entraîner des problèmes de sécurité lors de l'injection de scripts. Auparavant, des méthodes incluaient l'utilisation de DocumentFragment, mais elles posaient également d'autres problèmes, comme le chargement d'images et l'exécution de scripts lors de la définition des modèles, ainsi que des obstacles à la réutilisabilité. C'est là que l'élément <template> entre en jeu. les modèles fournissent le DOM inert, une méthode hautement performante pour cloner des nœuds, ainsi que des modèles réutilisables.

Utiliser des modèles

Ensuite, effectuez la transition du composant pour qu'il utilise des 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 DOM vers un tag de modèle dans le DOM du document principal. Maintenant, refactorisez 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 devez interroger le modèle, récupérer son contenu, puis cloner ces nœuds avec templateContent.cloneNode, où l'argument true effectue un clone profond. Vous initialisez ensuite le dom avec les données.

Félicitations, vous disposez désormais d'un composant Web ! Malheureusement, il ne fait rien pour l'instant. Vous allez maintenant ajouter quelques fonctionnalités.

6. Ajouter des fonctionnalités

Liaisons de propriétés

Actuellement, le seul moyen de définir la note dans l'élément rating est de créer l'élément, de définir la propriété rating sur l'objet, puis de l'ajouter à la page. Malheureusement, les éléments HTML natifs ne fonctionnent pas de cette façon. Les éléments HTML natifs ont tendance à être mis à jour lorsque les propriétés et les attributs ont été modifiés.

Pour que l'élément personnalisé mette à jour la vue lorsque la propriété rating change, ajoutez 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é rating, puis vous mettez à jour le texte de l'élément rating s'il est disponible. Cela signifie que si vous définissez la propriété rating sur l'élément, la vue est mise à jour. effectuez un test rapide dans la console des outils de développement.

Liaisons d'attributs

Maintenant, mettez à jour la vue lorsque l'attribut change : cela s'apparente à une entrée mettant à jour sa vue lorsque vous définissez <input value="newValue">. Heureusement, le cycle de vie du composant Web inclut attributeChangedCallback. Modifiez 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.

Fonctionnement du bouton

Il ne manque plus que la fonctionnalité du bouton. Le comportement de ce composant doit permettre à l'utilisateur de fournir une seule note positive ou négative, et de fournir un retour visuel à l'utilisateur. Vous pouvez l'implémenter avec certains écouteurs d'événements et une propriété de réflexion, mais commencez par modifier les styles pour obtenir 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 le 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 "up", le bouton "J'aime" s'affiche en vert, mais 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 réfléchi pour vote, comme vous avez implémenté rating. Commencez par les setter et 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, puis dans le setter, vous vérifiez si la nouvelle valeur est différente. Si tel est le cas, ajustez la note en conséquence et, surtout, renvoyez l'attribut vote à l'hôte avec this.setAttribute.

Configurez ensuite la liaison d'attributs:

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;
  }
}

Encore une fois, le processus est le même que pour 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 que les boutons fonctionnent correctement.

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 devez lier des écouteurs de clics à l'élément et conserver les références. Dans connectedCallback, vous écoutez les événements de clic sur les boutons. Dans disconnectedCallback, vous nettoyez ces écouteurs, et vous 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 quelques boutons ! Le problème est que mon fichier JS atteint désormais 96 lignes, mon fichier HTML 43 lignes, et que le code est assez détaillé 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, le tag <template> est utile et performant, mais il n'est pas empaqueté avec la logique du composant. Il est donc difficile de distribuer le modèle avec le reste de la logique. De plus, la façon dont les éléments de modèle sont intrinsèquement utilisés au code impératif, ce qui entraîne, dans de nombreux cas, un code moins lisible que les modèles de codage 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 d'afficher et de réafficher efficacement ces modèles avec des données pour créer et mettre à jour le DOM. Elle est semblable aux bibliothèques JSX et VDOM populaires, mais elle s'exécute de manière native dans le navigateur et est beaucoup plus efficace dans de nombreux cas.

Utiliser Lit HTML

Ensuite, migrez le composant Web natif rating-element pour utiliser le modèle Lit qui utilise des littéraux de modèle tagué, qui sont des fonctions qui utilisent 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 fournit certaines fonctionnalités de nettoyage pour des raisons de sécurité. Commencez par migrer le <template> de 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 de 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 avec balises 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 du littéral de modèle de ${...}. Cela signifie que, à terme, vous n'aurez plus besoin de mettre à jour ce nœud de façon impérative. De plus, vous appelez la méthode render éclairée, 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 méthode render nouvellement définie. Vous pouvez commencer par utiliser la liaison d'écouteur d'événements de Lit pour effacer 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. Ici, 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, ainsi que dans 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 de l'écouteur de clics des trois rappels, et même supprimer complètement disconnectedCallback. Vous avez également supprimé tout le code d'initialisation 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 être mis à 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 des DOM du setter rating et ajouté un appel à render à partir du setter vote. Le modèle est désormais bien plus lisible, car vous pouvez voir où les liaisons et les écouteurs d'événements sont appliqués.

Actualisez la page. Vous devriez avoir un bouton d'évaluation fonctionnel qui devrait ressembler à ceci lorsque vous appuyez sur le bouton de vote pour.

Curseur de &quot;J&#39;aime&quot; ou &quot;Je n&#39;aime pas&quot; d&#39;une valeur de 6, et le pouce vers le haut en vert

8. LitElement

Pourquoi utiliser LitElement

Le code présente toujours des problèmes. 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 rendu soient essentiellement no-op et efficaces, la VM JavaScript passe encore du temps à appeler cette fonction de manière synchrone. Deuxièmement, l'ajout de propriétés et d'attributs est fastidieux, car cela nécessite beaucoup de code récurrent. C'est là que LitElement entre en jeu !

LitElement est la classe de base de Lit qui permet de créer des composants Web rapides et légers qui peuvent être utilisés dans tous les frameworks et environnements. Voyons maintenant 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()
...

Importez LitElement, qui est la nouvelle classe de base de rating-element. Conservez ensuite votre importation html, puis css, qui nous permet de définir des littéraux de modèles balisés CSS pour les mathématiques, la création de modèles et d'autres fonctionnalités en arrière-plan.

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 utilise ces styles et utilise des fonctionnalités de navigateur telles que les feuilles de style constructibles pour accélérer l'affichage et les transmet si nécessaire via le polyfill des composants Web dans les navigateurs plus anciens.

Cycle de vie

Lit introduit un ensemble de méthodes de rappel de cycle de vie du 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éclenchent le cycle de vie du rendu LitElement, et vous définissez 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 met automatiquement à jour l'attribut vote de l'élément hôte que vous avez déclenché manuellement dans le setter vote.

Maintenant que vous disposez du bloc de propriétés statiques, vous pouvez supprimer l'ensemble de la logique de mise à jour du rendu des attributs et des propriétés. Cela signifie que vous pouvez supprimer les méthodes suivantes:

  • connectedCallback
  • observedAttributes
  • attributeChangedCallback
  • rating (setters et getters)
  • vote (setters et getters, mais conservent la logique de modification du setter)

Vous conservez le 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, il vous suffit d'initialiser rating et vote, puis de déplacer la logique 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é et rend l'affichage asynchrone. Les modifications apportées aux propriétés réactives (telles que 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 rechercher 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 venez de créer entièrement un composant Web et de le faire évoluer en LitElement.

Lit est très petit (moins de 5 Ko minifié + compressé avec gzip), super rapide et très amusant à coder ! Vous pouvez créer des composants qui seront utilisés par d'autres frameworks, ou l'utiliser pour créer des applications à part entière.

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 le comparer.

Et ensuite ?

Découvrez d'autres ateliers de programmation.

Complément d'informations

Communauté