WebComponents comme frameworks, interaction de composants

En ce qui concerne les composants Web, ils disent souvent: «Que voulez-vous sans frameworks? Tout y est prêt. » En fait, il existe des cadres créés sur la base de la mise en œuvre des normes incluses dans le groupe de composants Web. Il en existe même de relativement bons comme le X-Tag . Mais aujourd'hui, nous comprendrons toujours à quel point l'API du navigateur est devenue simple, élégante et puissante pour résoudre les tâches de développement quotidiennes, notamment en organisant l'interaction des composants entre eux et avec d'autres objets dans le contexte de l'exécution du navigateur, et vous pouvez toujours utiliser des cadres avec des composants Web, même celles qui ont été développées à travers les normes, notamment à travers les mécanismes que nous analyserons aujourd'hui, au moins comme indiqué sur le site .

Des décisions comme un réactif nous ont appris qu'il devrait y avoir un côté Big Data et les composants signés pour ses changements, et dans les composants Web, ils essaient également de discerner l'absence d'un tel sous-système, en fait, ce qui implique même un binning inversé inconsciemment et il existe déjà dans les composants Web.

Chaque élément a des attributs dont nous pouvons changer la valeur. Et si vous répertoriez les noms dans le crochet observéAttributes, puis quand ils changent, attributeChangedCallback () sera automatiquement appelé dans lequel nous pouvons déterminer le comportement du composant lorsque l'attribut change. En utilisant la magie getter, il est facile de faire une liaison inverse de la même manière.

Nous avons déjà esquissé un projet dans la première partie et aujourd'hui nous allons continuer à le couper davantage.

Il convient de mentionner immédiatement une limitation, au moins dans l'implémentation actuelle, selon laquelle la valeur d'attribut ne peut être qu'une valeur primitive spécifiée par un littéral et réductible à une chaîne, mais en général, il est possible de transférer des «objecs» de cette manière en utilisant des guillemets simples de l'extérieur et des doubles pour définir valeurs et champs du projet.

<my-component my='{"foo": "bar"}'></my-component> 

Pour utiliser cette valeur dans le code, vous pouvez implémenter un getter magique automatique qui appellera JSON.parse () dessus .

Mais pour l'instant, la valeur numérique du compteur nous suffit.

Nous ajoutons un nouvel attribut à notre élément, le spécifions comme observé, cliquez sur le gestionnaire de clics pour incrémenter ce compteur et ajoutons la mise à jour de la valeur affichée par lien direct au crochet de modification, dont la logique est implémentée dans une méthode updateLabel () réutilisable distincte .

 export class MyWebComp extends HTMLElement { constructor() { super(); } connectedCallback() { let html = document.importNode(myWebCompTemplate.content, true); this.attachShadow({mode: 'open'}); this.shadowRoot.appendChild(html); this.updateLabel(); } updateLabel() { this.shadowRoot.querySelector('#helloLabel').textContent = 'Hello ' + this.getAttribute('greet-name') + ' ' + this.getAttribute('count'); } static get observedAttributes() { return ['count']; } attributeChangedCallback(name, oldValue, newValue) { if (name === 'count') { this.updateLabel(); } } showMessage(event) { this.setAttribute('count', this.getAttribute('count') + 1); } } 



Chaque élément a reçu un compteur indépendant, mis à jour automatiquement.

Devoirs: implémentez la conversion du compteur en nombre et utilisez-le via un auto-getter;)

Bien sûr, des options plus avancées sont possibles. Par exemple, si vous ne prenez pas en compte les rappels et événements simples, en utilisant le proxy natif et les mêmes getters et setters, vous pouvez implémenter la fonctionnalité de liaison inverse.

Ajouter le fichier my-counter.js avec une classe de ce type

 export class MyCounter extends EventTarget { constructor() { super(); this.count = 0; } increment() { this.count++; this.dispatchEvent(new CustomEvent('countChanged', { detail: { count: this.count } })); } } 

Nous avons hérité d'une classe de EventTarget afin que d'autres classes puissent s'abonner aux événements lancés par les objets de cette classe et définir une propriété count qui stockera la valeur du compteur.

Ajoutez maintenant l'instance de cette classe en tant que propriété statique pour le composant.

 <script type="module"> import { MyWebComp } from "./my-webcomp.js"; import { MyCounter } from "./my-counter.js"; let counter = new MyCounter(); Object.defineProperty(MyWebComp.prototype, 'counter', { value: counter }); customElements.define('my-webcomp', MyWebComp); </script> 

Et dans le code du composant, nous souscrirons à la valeur change la méthode de mise à jour des étiquettes updateLabel () , à laquelle nous ajoutons l'affichage de la valeur du compteur partagé global. Et dans le gestionnaire de clics, un appel direct à la méthode incrémentielle.

 export class MyWebComp extends HTMLElement { constructor() { super(); } connectedCallback() { let html = document.importNode(myWebCompTemplate.content, true); this.attachShadow({mode: 'open'}); this.shadowRoot.appendChild(html); this.updateLabel(); this.counter.addEventListener('countChanged', this.updateLabel.bind(this)); } updateLabel() { this.shadowRoot.querySelector('#helloLabel').textContent = 'Hello ' + this.getAttribute('greet-name') + ' ' + this.getAttribute('count') + ' ' + this.counter.count; } static get observedAttributes() { return ['count']; } attributeChangedCallback(name, oldValue, newValue) { if (name === 'count') { if (this.shadowRoot) { this.updateLabel(); } } } showMessage(event) { this.setAttribute('count', parseInt(this.getAttribute('count')) + 1); this.counter.increment(); } } 

Malgré le fait que chaque élément a reçu deux compteurs, chacune de ses valeurs sera incrémentée indépendamment, mais la valeur du compteur partagé sera la même pour les deux éléments, et les valeurs individuelles seront déterminées par le nombre de clics uniquement sur eux.



Ainsi, nous avons obtenu une liaison directe en liaison directe, et en raison de l'utilisation d'événements, cette valeur est faible lors de la mise à jour. Bien sûr, rien n'empêche l'incrément d'être implémenté via des événements, en liant la méthode increment () au lyseur de l'événement:

 export class MyCounter extends EventTarget { constructor() { super(); this.count = 0; this.addEventListener('increment', this.increment.bind(this)); } increment() { this.count++; this.dispatchEvent(new CustomEvent('countChanged', { detail: { count: this.count } })); } } 

et remplacer l'appel de méthode par un lancer d'événement:

 export class MyWebComp extends HTMLElement { ... showMessage(event) { this.setAttribute('count', parseInt(this.getAttribute('count')) + 1); this.counter.dispatchEvent(new CustomEvent('increment')); } } 

Qu'est-ce que cela change? Maintenant, si au cours du développement, la méthode increment () est supprimée ou modifiée, l'exactitude de notre code sera violée, mais il n'y aura pas d'erreurs d'interprétation, c'est-à-dire l'opérabilité restera. Cette caractéristique est appelée faible connectivité.

Dans le développement, une liaison directe et faible est nécessaire, généralement il est habituel d'implémenter une liaison directe dans la logique d'un module de composant, et un couplage faible entre différents modules et composants afin de fournir la flexibilité et l'extensibilité de l'ensemble du système. La sémantique HTML implique un haut niveau de flexibilité, c'est-à-dire préférence pour une connectivité faible, mais l'application fonctionnera de manière plus fiable si les connexions à l'intérieur des composants sont directes.

Nous pouvons raccrocher les gestionnaires à l'ancienne et appeler des événements sur l'objet document global.

 export class MyWebComp extends HTMLElement { constructor() { super(); } connectedCallback() { let html = document.importNode(myWebCompTemplate.content, true); this.attachShadow({mode: 'open'}); this.shadowRoot.appendChild(html); this.updateLabel(); this.counter.addEventListener('countChanged', this.updateLabel.bind(this)); document.addEventListener('countChanged', this.updateLabel.bind(this)); } disconnectedCallback() { document.removeEventListener(new CustomEvent('increment')); document.removeEventListener(new CustomEvent('countChanged')); } ... showMessage(event) { this.setAttribute('count', parseInt(this.getAttribute('count')) + 1); this.counter.dispatchEvent(new CustomEvent('increment')); document.dispatchEvent(new CustomEvent('increment')); } } 

 export class MyCounter extends EventTarget { constructor() { super(); this.count = 0; this.addEventListener('increment', this.increment.bind(this)); document.addEventListener('increment', this.increment.bind(this)); } increment() { this.count++; this.dispatchEvent(new CustomEvent('countChanged', { detail: { count: this.count } })); document.dispatchEvent(new CustomEvent('countChanged', { detail: { count: this.count } })); } } 

Le comportement n'est pas différent, mais maintenant nous devons penser à supprimer le gestionnaire d'événements de l'objet global si l'élément lui-même est supprimé de l'arborescence. Heureusement, les composants Web nous fournissent non seulement des crochets de conception, mais aussi une destruction, évitant les fuites de mémoire.

Il y a aussi un autre point important: les événements des éléments arborescents peuvent interagir les uns avec les autres grâce à un mécanisme appelé «bouillonnement».

Lorsque l'utilisateur clique sur notre élément, l'événement, ayant travaillé sur l'élément lui-même, commence à être appelé sur le parent comme des cercles sur l'eau, puis sur le parent de ce parent et ainsi de suite sur l'élément racine.

À tout moment de cette chaîne d'appels, l'événement peut être intercepté et traité.

Bien sûr, ce n'est pas tout à fait le même événement, et ses dérivés et contextes, bien qu'ils contiennent des liens les uns avec les autres, par exemple dans event.path , ne coïncideront pas complètement.

Cependant, ce mécanisme vous permet d'associer des éléments enfants à leurs parents de telle manière que ce lien ne viole pas les modèles d'ingénierie, c'est-à-dire sans liens directs, mais uniquement en raison de leur propre comportement.

Dans les temps anciens, cela a également donné lieu à de nombreux problèmes, lorsque les événements de certains éléments ont provoqué des «cercles» ou des échos dans certaines sections du document qui n'étaient pas souhaitables pour les réactions.

Pendant des années, les développeurs Web ont lutté avec la «propagande» (propagation ou surfaçage) des événements et les ont arrêtés et les ont transformés comme ils le pouvaient, mais pour leur utilisation confortable, comme dans le cas des identifiants, seule l'isolement de l'arbre fantôme faisait défaut.

La magie ici est que les événements ne pillent pas en dehors de l'arbre des ombres. Mais néanmoins, vous pouvez attraper l'événement déclenché par ses enfants dans le composant hôte et le remonter dans l'arborescence sous la forme d'une autre signification d'événement, eh bien, ou simplement le traiter.

Pour comprendre comment tout cela fonctionne, nous emballons nos éléments webcomp dans un conteneur comme celui-ci:

 <my-webcont count=0> <my-webcomp id="myWebComp" greet-name="John" count=0 onclick="this.showMessage(event)"></my-webcomp> <my-webcomp id="myWebComp2" greet-name="Josh" count=0 onclick="this.showMessage(event)"></my-webcomp> </my-webcont> 

Le conteneur aura ce code:

 export class MyWebCont extends HTMLElement { constructor() { super(); } connectedCallback() { this.addEventListener('increment', this.updateCount.bind(this)); } updateCount(event) { this.setAttribute('count', parseInt(this.getAttribute('count')) + 1); } static get observedAttributes() { return ['count']; } attributeChangedCallback(name, oldValue, newValue) { if (name === 'count') { this.querySelectorAll('my-webcomp').forEach((el) => { el.setAttribute('count', newValue); }); } } } 

Dans connectedCallback (), nous accrocherons le gestionnaire à l'événement d' incrémentation , qui lancera les enfants. Le gestionnaire incrémentera le propre compteur de l'élément, et un rappel pour modifier sa valeur passera par tous les éléments enfants et incrémentera leurs compteurs, sur lesquels les gestionnaires précédemment développés par nous se bloquent.

Le code des éléments enfants changera légèrement, en fait, tout ce dont nous avons besoin est que l'événement incrémente jette l'élément lui-même et non ses agrégats, et le fasse avec l'attribut bubble: true .

 export class MyWebComp extends HTMLElement { ... showMessage(event) { this.setAttribute('count', parseInt(this.getAttribute('count')) + 1); this.counter.dispatchEvent(new CustomEvent('increment')); this.dispatchEvent(new CustomEvent('increment', { bubbles: true })); document.dispatchEvent(new CustomEvent('increment')); } } 



Maintenant, malgré un caractère aléatoire général, les premiers compteurs d'éléments afficheront toujours la valeur du parent. Dans l'ensemble, ce que nous avons fait ici aujourd'hui n'est certainement pas un exemple à suivre, mais seulement un aperçu des possibilités et des techniques par lesquelles vous pouvez organiser une interaction vraiment modulaire entre les composants, en évitant un minimum d'outils.

Vous pouvez trouver le code fini pour la référence dans le même référentiel, dans le brunch des événements .

Source: https://habr.com/ru/post/fr461153/


All Articles