[Conseiller la lecture] Les 19 autres parties du cycle Nous présentons à votre attention une traduction de 19 articles de la série de documents
SessionStack sur les caractéristiques des différents mécanismes de l'écosystème JavaScript. Aujourd'hui, nous allons parler de la norme des éléments personnalisés - les soi-disant «éléments personnalisés». Nous parlerons des tâches qu'ils permettent de résoudre et comment les créer et les utiliser.
Revue
Dans l'un des articles précédents de cette série, nous avons parlé du
DOM fantôme et de certaines autres technologies qui font partie d'un phénomène plus vaste - les composants Web. Les composants Web sont conçus pour permettre aux développeurs d'étendre les fonctionnalités standard de HTML en créant des éléments compacts, modulaires et réutilisables. Il s'agit de la norme W3C relativement nouvelle que les fabricants de tous les principaux navigateurs ont déjà remarquée. On le retrouve en production, bien que, bien sûr, alors que son travail est assuré par des polyphiles (on en parlera plus tard).
Comme vous le savez peut-être déjà, les navigateurs nous fournissent certains outils essentiels pour développer des sites Web et des applications Web. Il s'agit de HTML, CSS et JavaScript. Le HTML est utilisé pour structurer les pages Web, grâce au CSS, elles sont jolies et JavaScript est responsable des fonctionnalités interactives. Cependant, avant l'apparition des composants Web, il n'était pas si facile d'associer des actions implémentées JavaScript à une structure HTML.
En fait, nous examinerons ici la base des composants Web - Éléments personnalisés. En résumé, l'API conçue pour fonctionner avec eux permet au programmeur de créer ses propres éléments HTML avec une logique et des styles JavaScript intégrés décrits par CSS. Beaucoup confondent les éléments personnalisés avec la technologie Shadow DOM. Cependant, ce sont deux choses complètement différentes qui, en fait, se complètent, mais ne sont pas interchangeables.
Certains frameworks (comme Angular ou React) tentent de résoudre le même problème que les éléments personnalisés résolvent en introduisant leurs propres concepts. Les éléments personnalisés peuvent être comparés aux directives angulaires ou aux composants React. Cependant, les éléments personnalisés sont une fonctionnalité standard du navigateur; vous n'avez pas besoin d'utiliser autre chose que JavaScript, HTML et CSS ordinaire pour les utiliser. Bien sûr, cela ne nous permet pas de dire qu'ils remplacent les frameworks JS ordinaires. Les frameworks modernes nous offrent bien plus que la possibilité de simuler le comportement des éléments personnalisés. En conséquence, nous pouvons dire que les frameworks et les éléments utilisateur sont des technologies qui peuvent être utilisées ensemble pour résoudre des tâches de développement Web.
API
Avant de continuer, voyons quelles opportunités l'API nous offre pour travailler avec des éléments personnalisés. À savoir, nous parlons d'un objet global
customElements
qui a plusieurs méthodes:
- La méthode
define(tagName, constructor, options)
vous permet de définir (créer, enregistrer) un nouvel élément utilisateur. Il prend trois arguments - le nom de la balise pour l'élément utilisateur, correspondant aux règles de dénomination de ces éléments, une déclaration de classe et un objet avec des paramètres. Actuellement, un seul paramètre est pris en charge - extends
, qui est une chaîne qui spécifie le nom de l'élément en ligne à développer. Cette fonction est utilisée pour créer des versions spéciales d'éléments standard. - La méthode
get(tagName)
renvoie le constructeur de l'élément utilisateur, à condition que cet élément soit déjà défini, sinon il renvoie undefined
. Il prend un argument - la balise de nom de l'élément utilisateur. - La
whenDefined(tagName)
renvoie la promesse qui est résolue après la création de l'élément utilisateur. Si un élément est déjà défini, cette promesse est résolue immédiatement. Une promesse est rejetée si le nom de balise qui lui est transmis n'est pas un nom de balise valide pour l'élément utilisateur. Cette méthode accepte le nom de balise de l'élément utilisateur.
Créer des éléments personnalisés
La création d'éléments personnalisés est très simple. Pour ce faire, deux choses doivent être faites: créer une déclaration de classe pour l'élément qui doit étendre la classe
HTMLElement
et enregistrer cet élément sous le nom sélectionné. Voici à quoi ça ressemble:
class MyCustomElement extends HTMLElement { constructor() { super();
Si vous ne souhaitez pas polluer la portée actuelle, vous pouvez utiliser une classe anonyme:
customElements.define('my-custom-element', class extends HTMLElement { constructor() { super();
Comme vous pouvez le voir dans les exemples, l'élément utilisateur est enregistré à l'aide de la
customElements.define(...)
vous connaissez déjà.
Problèmes résolus par les éléments personnalisés
Parlons des problèmes qui nous permettent de résoudre des éléments personnalisés. L'une d'elles consiste à améliorer la structure du code et à éliminer ce qu'on appelle une «soupe à balises div» (soupe soup). Ce phénomène est une structure de code très courante dans les applications Web modernes, dans lesquelles de nombreux éléments
div
sont intégrés les uns aux autres. Voici à quoi cela pourrait ressembler:
<div class="top-container"> <div class="middle-container"> <div class="inside-container"> <div class="inside-inside-container"> <div class="are-we-really-doing-this"> <div class="mariana-trench"> … </div> </div> </div> </div> </div> </div>
Un tel code HTML est utilisé pour des raisons justifiables - il décrit la mise en page de la page et garantit son affichage correct à l'écran. Cependant, cela nuit à la lisibilité du code HTML et complique sa maintenance.
Supposons que nous ayons un composant qui ressemble à la figure suivante.
Apparence des composantsEn utilisant l'approche traditionnelle pour décrire de telles choses, le code suivant correspondra à ce composant:
<div class="primary-toolbar toolbar"> <div class="toolbar"> <div class="toolbar-button"> <div class="toolbar-button-outer-box"> <div class="toolbar-button-inner-box"> <div class="icon"> <div class="icon-undo"> </div> </div> </div> </div> </div> <div class="toolbar-button"> <div class="toolbar-button-outer-box"> <div class="toolbar-button-inner-box"> <div class="icon"> <div class="icon-redo"> </div> </div> </div> </div> </div> <div class="toolbar-button"> <div class="toolbar-button-outer-box"> <div class="toolbar-button-inner-box"> <div class="icon"> <div class="icon-print"> </div> </div> </div> </div> </div> <div class="toolbar-toggle-button toolbar-button"> <div class="toolbar-button-outer-box"> <div class="toolbar-button-inner-box"> <div class="icon"> <div class="icon-paint-format"> </div> </div> </div> </div> </div> </div> </div>
Imaginez maintenant que nous pourrions, au lieu de ce code, utiliser cette description du composant:
<primary-toolbar> <toolbar-group> <toolbar-button class="icon-undo"></toolbar-button> <toolbar-button class="icon-redo"></toolbar-button> <toolbar-button class="icon-print"></toolbar-button> <toolbar-toggle-button class="icon-paint-format"></toolbar-toggle-button> </toolbar-group> </primary-toolbar>
Je suis sûr que tout le monde sera d'accord pour dire que le deuxième fragment de code semble beaucoup mieux. Un tel code est plus facile à lire, plus facile à entretenir et est compréhensible à la fois pour le développeur et le navigateur. Tout se résume au fait qu'il est plus simple que celui dans lequel il existe de nombreuses balises
div
imbriquées.
Le problème suivant qui peut être résolu avec des éléments personnalisés est la réutilisation du code. Le code que les développeurs écrivent devrait non seulement fonctionner, mais également être pris en charge. La réutilisation du code, par opposition à l'écriture constante des mêmes constructions, améliore les capacités de support de projet.
Voici un exemple simple qui vous aidera à mieux comprendre cette idée. Supposons que nous ayons l'élément suivant:
<div class="my-custom-element"> <input type="text" class="email" /> <button class="submit"></button> </div>
Si vous en avez constamment besoin, alors, avec l'approche habituelle, nous devrons écrire le même code HTML encore et encore. Imaginez maintenant que vous devez apporter une modification à ce code qui devrait se refléter partout où il est utilisé. Cela signifie que nous devons trouver tous les endroits où ce fragment est utilisé, puis effectuer les mêmes changements partout. Il est long, dur et semé d'erreurs.
Ce serait beaucoup mieux si nous pouvions où cet élément est nécessaire, il suffit d'écrire ce qui suit:
<my-custom-element></my-custom-element>
Cependant, les applications Web modernes sont bien plus qu'un HTML statique. Ils sont interactifs. La source de leur interactivité est JavaScript. Habituellement, pour fournir de telles capacités, certains éléments sont créés, puis des écouteurs d'événements y sont connectés, ce qui leur permet de répondre aux influences des utilisateurs. Par exemple, ils peuvent répondre à des clics, au «survol» du pointeur de la souris sur eux, à les faire glisser sur l'écran, etc. Voici comment connecter un écouteur d'événements à un élément qui se produit lorsque vous cliquez dessus avec la souris:
var myDiv = document.querySelector('.my-custom-element'); myDiv.addEventListener('click', _ => { myDiv.innerHTML = '<b> I have been clicked </b>'; });
Et voici le code HTML de cet élément:
<div class="my-custom-element"> I have not been clicked yet. </div>
En utilisant l'API pour travailler avec des éléments personnalisés, toute cette logique peut être incluse dans l'élément lui-même. À titre de comparaison, voici le code pour déclarer un élément personnalisé qui inclut un gestionnaire d'événements:
class MyCustomElement extends HTMLElement { constructor() { super(); var self = this; self.addEventListener('click', _ => { self.innerHTML = '<b> I have been clicked </b>'; }); } } customElements.define('my-custom-element', MyCustomElement);
Et voici à quoi cela ressemble dans le code HTML de la page:
<my-custom-element> I have not been clicked yet </my-custom-element>
À première vue, il peut sembler que davantage de lignes de code JS sont nécessaires pour créer un élément personnalisé. Cependant, dans des applications réelles, il arrive rarement que de tels éléments ne soient créés que pour être utilisés une seule fois. Un autre phénomène typique des applications Web modernes est que la plupart des éléments qu'elles contiennent sont créés dynamiquement. Cela conduit à la nécessité de prendre en charge deux scénarios différents d'utilisation des éléments: les situations où ils sont ajoutés dynamiquement à la page à l'aide de JavaScript et les situations où ils sont décrits dans la structure HTML d'origine de la page. Grâce à l'utilisation d'éléments personnalisés, le travail dans ces deux situations est simplifié.
Par conséquent, si nous résumons les résultats de cette section, nous pouvons dire que les éléments utilisateur rendent le code plus clair, simplifient sa prise en charge, aident à le décomposer en petits modules, qui incluent toutes les fonctionnalités nécessaires et peuvent être réutilisés.
Maintenant que nous avons discuté des problèmes généraux liés à l'utilisation d'éléments personnalisés, parlons de leurs fonctionnalités.
Prérequis
Avant de commencer à développer vos propres éléments personnalisés, vous devez connaître certaines des règles que vous devez suivre lors de leur création. Les voici:
- Le nom du composant doit inclure un trait d'union (symbole
-
). Grâce à cela, l'analyseur HTML peut distinguer les éléments intégrés et les éléments utilisateur. De plus, cette approche garantit qu'il n'y a pas de collision de noms avec des éléments intégrés (à la fois avec ceux qui sont maintenant et avec ceux qui apparaîtront à l'avenir). Par exemple, le nom réel de l'élément personnalisé est >my-custom-element<
, et les noms >myCustomElement<
et <my_custom_element>
ne conviennent pas. - Il est interdit d'enregistrer la même balise plus d'une fois. Si vous tentez de le faire, le navigateur
DOMException
erreur DOMException
. Les éléments personnalisés ne peuvent pas être redéfinis. - Les balises personnalisées ne peuvent pas se fermer automatiquement. L'analyseur HTML ne prend en charge qu'un ensemble limité de balises à fermeture automatique standard (par exemple,
<img>
, <link>
, <br>
).
Les possibilités
Parlons de ce que vous pouvez faire avec des éléments personnalisés. Si vous répondez à cette question en un mot, il s'avère que vous pouvez faire beaucoup de choses intéressantes avec eux.
L'une des caractéristiques les plus notables des éléments personnalisés est que la déclaration d'une classe d'élément fait référence à l'élément DOM lui-même. Cela signifie que vous pouvez utiliser le mot clé this dans une annonce pour connecter des écouteurs d'événements, pour accéder aux propriétés, aux nœuds enfants, etc.
class MyCustomElement extends HTMLElement {
Ceci, bien sûr, permet d'écrire de nouvelles données sur les nœuds enfants de l'élément. Cependant, cette opération n'est pas recommandée, car cela peut entraîner un comportement inattendu des éléments. Si vous imaginez que vous utilisez des éléments conçus par quelqu'un d'autre, vous serez probablement surpris si votre propre balisage placé dans l'élément est remplacé par autre chose.
Il existe plusieurs méthodes qui vous permettent d'exécuter du code à certains moments du cycle de vie d'un élément.
- La méthode
constructor
est appelée une fois, lors de la création ou de la «mise à niveau» de l'élément (nous en parlerons ci-dessous). Le plus souvent, il est utilisé pour initialiser l'état d'un élément, pour connecter des écouteurs d'événements, créer un DOM fantôme, etc. N'oubliez pas que vous devez toujours appeler super()
dans le constructeur. - La méthode
connectedCallback
est appelée chaque fois qu'un élément est ajouté au DOM. Il peut être utilisé (et c'est précisément la façon dont il est recommandé de l'utiliser) afin de reporter l'exécution de toute action jusqu'au moment où l'élément apparaît sur la page (par exemple, de cette façon, vous pouvez retarder le chargement de certaines données). - La méthode
disconnectedCallback
est appelée lorsqu'un élément est supprimé du DOM. Il est généralement utilisé pour libérer des ressources. Notez que cette méthode n'est pas appelée si l'utilisateur ferme l'onglet du navigateur avec la page. Par conséquent, ne comptez pas sur lui lorsque cela est nécessaire pour effectuer certaines actions particulièrement importantes. - La méthode
attributeChangedCallback
est appelée lorsqu'un attributeChangedCallback
élément est ajouté, supprimé, mis à jour ou remplacé. De plus, il est appelé lorsque l'élément est créé par l'analyseur. Cependant, notez que cette méthode s'applique uniquement aux attributs répertoriés dans la propriété observedAttributes
. - La méthode
adoptedCallback
appelée lorsque la méthode document.adoptNode(...)
est utilisée, qui est utilisée pour déplacer le nœud vers un autre document.
Veuillez noter que toutes les méthodes ci-dessus sont synchrones. Par exemple, la méthode
connectedCallback
est appelée immédiatement après l'ajout de l'élément au DOM et le reste du programme attend la fin de cette méthode.
Réflexion de propriété
Les éléments HTML intégrés ont une fonctionnalité très pratique: la réflexion des propriétés. Grâce à ce mécanisme, les valeurs de certaines propriétés sont directement reflétées dans le DOM en tant qu'attributs. Disons que cela est caractéristique de la propriété
id
. Par exemple, nous effectuons l'opération suivante:
myDiv.id = 'new-id';
Les modifications pertinentes affecteront le DOM:
<div id="new-id"> ... </div>
Ce mécanisme fonctionne en sens inverse. Il est très utile car il vous permet de configurer des éléments de manière déclarative.
Les éléments personnalisés n'ont pas cette fonctionnalité intégrée, mais vous pouvez l'implémenter vous-même. Pour que certaines propriétés des éléments utilisateur se comportent de manière similaire, vous pouvez configurer leurs getters et setters.
class MyCustomElement extends HTMLElement {
Extension d'éléments existants
L'API des éléments personnalisés vous permet non seulement de créer de nouveaux éléments HTML, mais également d'étendre ceux existants. De plus, nous parlons à la fois d'éléments standard et d'éléments personnalisés. Cela se fait en utilisant le
extends
lors de la déclaration d'une classe:
class MyAwesomeButton extends MyButton {
Les éléments standard étendus sont également appelés «élément intégré personnalisé».
Il est recommandé d'en faire une règle pour toujours développer les éléments existants et le faire progressivement. Cela vous permettra d'enregistrer dans de nouveaux éléments les capacités qui ont été implémentées dans des éléments créés précédemment (c'est-à-dire, propriétés, attributs, fonctions).
Veuillez noter que les éléments intégrés personnalisés ne sont désormais pris en charge que dans Chrome 67+. Cela apparaîtra dans d'autres navigateurs, cependant, il est connu que les développeurs de Safari ont décidé de ne pas mettre en œuvre cette opportunité.
Mettre à jour les éléments
Comme déjà mentionné, la
customElements.define(...)
est utilisée pour enregistrer des éléments personnalisés. Cependant, l'enregistrement ne peut pas être appelé l'action qui doit être effectuée en premier lieu. L'enregistrement de l'élément utilisateur peut être différé pendant un certain temps, de plus, cette fois peut arriver même lorsque l'élément est déjà ajouté au DOM. Ce processus est appelé mise à niveau. Pour savoir quand un élément sera enregistré, le navigateur fournit la
customElements.whenDefined(...)
. Il reçoit le nom de la balise d'élément et renvoie la promesse qui est résolue après l'enregistrement de l'élément.
customElements.whenDefined('my-custom-element').then(_ => { console.log('My custom element is defined'); });
Par exemple, vous devrez peut-être retarder l'enregistrement d'un élément jusqu'à ce que ses enfants soient déclarés. Une telle ligne de comportement peut être extrêmement utile si le projet a des éléments utilisateur imbriqués. Parfois, un parent peut compter sur l'implémentation d'éléments enfants. Dans ce cas, vous devez vous assurer que les enfants sont enregistrés avant le parent.
Ombre dom
Comme déjà mentionné, les éléments personnalisés et le Shadow DOM sont des technologies complémentaires. Le premier vous permet d'encapsuler la logique JS dans les éléments utilisateur, et le second vous permet de créer des environnements isolés pour les fragments DOM qui ne sont pas affectés par ce qui est en dehors d'eux. Si vous sentez que vous avez besoin de mieux comprendre le concept Shadow DOM, jetez un œil à l'une de nos
publications précédentes .
Voici comment utiliser le DOM fantôme pour un élément personnalisé:
class MyCustomElement extends HTMLElement {
Comme vous pouvez le voir, appeler
this.attachShadow
joue
this.attachShadow
un rôle clé.
Patterns
Dans l'un de nos articles
précédents , nous avons parlé un peu des modèles, bien qu'ils soient, en fait, dignes d'un article séparé. Nous allons voir ici un exemple simple de la façon d'incorporer des modèles dans des éléments personnalisés lorsqu'ils sont créés. Ainsi, en utilisant la
<template>
, vous pouvez décrire le fragment DOM qui sera traité par l'analyseur, mais ne sera pas affiché sur la page:
<template id="my-custom-element-template"> <div class="my-custom-element"> <input type="text" class="email" /> <button class="submit"></button> </div> </template>
Voici comment appliquer un modèle dans un élément personnalisé:
let myCustomElementTemplate = document.querySelector('#my-custom-element-template'); class MyCustomElement extends HTMLElement {
Comme vous pouvez le voir, il existe une combinaison d'un élément personnalisé, d'un DOM fantôme et de modèles. Cela nous a permis de créer un élément isolé dans son propre espace, dans lequel la structure HTML est séparée de la logique JS.
Stylisation
Jusqu'à présent, nous n'avons parlé que de JavaScript et HTML, en ignorant CSS. Par conséquent, nous abordons maintenant le sujet des styles. De toute évidence, nous avons besoin d'un moyen de styliser des éléments personnalisés. Des styles peuvent être ajoutés à l'intérieur du DOM Shadow, mais la question se pose alors de savoir comment styliser ces éléments de l'extérieur, par exemple - s'ils ne sont pas utilisés par la personne qui les a créés. La réponse à cette question est assez simple - les éléments personnalisés sont stylisés de la même manière que les éléments intégrés.
my-custom-element { border-radius: 5px; width: 30%; height: 50%;
Notez que les styles externes ont priorité sur les styles déclarés à l'intérieur d'un élément, les remplaçant.
Vous avez peut-être vu comment, lorsqu'une page était affichée à l'écran, à un moment donné, vous pouvez y observer du contenu non stylisé (c'est ce qu'on appelle FOUC - Flash Of Unstyled Content). Vous pouvez éviter ce phénomène en définissant des styles pour les composants non enregistrés et en utilisant certains effets visuels lors de leur enregistrement. Pour ce faire, vous pouvez utiliser le sélecteur
:defined
. Vous pouvez le faire, par exemple, comme ceci:
my-button:not(:defined) { height: 20px; width: 50px; opacity: 0; }
Éléments inconnus et éléments utilisateur non définis
La spécification HTML est très flexible, elle vous permet de déclarer toutes les balises dont vous avez besoin pour le développeur. Et, si la balise n'est pas reconnue par le navigateur, elle sera traitée par l'analyseur en tant que
HTMLUnknownElement
:
var element = document.createElement('thisElementIsUnknown'); if (element instanceof HTMLUnknownElement) { console.log('The selected element is unknown'); }
Cependant, lorsque vous travaillez avec des éléments personnalisés, un tel schéma ne s'applique pas. , ? , ,
HTMLElement
.
var element = document.createElement('this-element-is-undefined'); if (element instanceof HTMLElement) { console.log('The selected element is undefined but not unknown'); }
HTMLElement
HTMLUnknownElement
, , , , - . , , , .
div
. .
Chrome 36+. API Custom Components v0, , , , . API, , —
. API Custom Elements v1 Chrome 54+ Safari 10.1+ ( ). Mozilla v50, , . , Microsoft Edge API. , , webkit. , , , — IE 11.
, , ,
customElements
window
:
const supportsCustomElements = 'customElements' in window; if (supportsCustomElements) {
:
function loadScript(src) { return new Promise(function(resolve, reject) { const script = document.createElement('script'); script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); }
Résumé
, :
- HTML- JavaScript-, , CSS-.
- HTML- ( , ).
- . , — JavaScript, HTML, CSS, , , .
- - (Shadow DOM, , , ).
- , .
- , .
,
Custom Elements v1 , , , , , .
Chers lecteurs! ?
