Wenn es um Webkomponenten geht, sagen sie oft: „Was möchten Sie ohne Frameworks? Dort ist alles fertig. “ Tatsächlich gibt es Frameworks, die auf der Grundlage der Implementierung der in der Gruppe der Webkomponenten enthaltenen Standards erstellt wurden. Es gibt sogar relativ gute wie den
X-Tag . Heute werden wir jedoch noch verstehen, wie einfach, elegant und leistungsstark die Browser-API für die Lösung alltäglicher Entwicklungsaufgaben geworden ist, einschließlich der Organisation der Interaktion von Komponenten untereinander und mit anderen Objekten im Kontext der Ausführung des Browsers. Sie können auch jederzeit Frameworks mit Webkomponenten verwenden diejenigen, die über Standards hinweg entwickelt wurden, einschließlich der Mechanismen, die wir heute analysieren werden, zumindest wie
auf der Website angegeben .
Entscheidungen wie ein Reagenz haben uns gelehrt, dass es eine Big-Data-Seite geben sollte und die Komponenten für ihre Änderungen signiert sein sollten. In Webkomponenten versuchen sie auch, das Fehlen eines solchen Subsystems zu erkennen, was sogar ein unbewusstes umgekehrtes Binning impliziert und bereits in Webkomponenten vorhanden ist.
Jedes Element hat Attribute, deren Wert wir ändern können. Wenn Sie die Namen im
ObservatedAttributes- Hook auflisten, wird beim Ändern automatisch
attributeChangedCallback () aufgerufen, in dem wir das Verhalten der Komponente bestimmen können, wenn sich das Attribut ändert. Mit Getter Magic ist es einfach, auf ähnliche Weise eine umgekehrte Bindung durchzuführen.
Wir haben bereits im
ersten Teil ein Projekt skizziert und werden es heute weiter schneiden.
Es ist erwähnenswert, dass zumindest in der aktuellen Implementierung sofort eine Einschränkung erwähnt wird, dass der Attributwert nur ein primitiver Wert sein kann, der durch ein Literal angegeben und auf eine Zeichenfolge reduzierbar ist. Im Allgemeinen ist es jedoch möglich, "Objekte" auf diese Weise mit einfachen Anführungszeichen von außen zu übertragen und zu definieren Werte und Felder des Projekts.
<my-component my='{"foo": "bar"}'></my-component>
Um diesen Wert im Code zu verwenden, können Sie einen
automatischen Magic Getter implementieren, der
JSON.parse () aufruft .
Aber vorerst reicht uns der numerische Wert des Zählers.
Wir fügen unserem Element ein neues Attribut hinzu, geben es wie beobachtet an, klicken auf den Klick-Handler, um diesen Zähler zu erhöhen, und fügen die Aktualisierung des angezeigten Werts durch direkten Link zum Änderungs-Hook hinzu, dessen Logik in einer separaten
wiederverwendbaren updateLabel () -Methode implementiert ist.
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); } }

Jeder Artikel erhielt einen unabhängigen, automatisch aktualisierten Zähler.
Hausaufgabe: Konvertieren Sie den Zähler in eine Zahl und verwenden Sie ihn über einen Auto-Getter;)
Natürlich sind erweiterte Optionen möglich. Wenn Sie beispielsweise einfache Rückrufe und Ereignisse nicht berücksichtigen, indem Sie den nativen Proxy und dieselben Getter und Setter verwenden, können Sie die Funktionalität der umgekehrten Bindung implementieren.
Fügen
Sie die Datei
my-counter.js mit einer Klasse dieser Art hinzu
export class MyCounter extends EventTarget { constructor() { super(); this.count = 0; } increment() { this.count++; this.dispatchEvent(new CustomEvent('countChanged', { detail: { count: this.count } })); } }
Wir haben eine Klasse von
EventTarget geerbt, damit andere Klassen Ereignisse abonnieren können, die von Objekten dieser Klasse ausgelöst werden, und eine count-Eigenschaft definieren können, in der der
Zählerwert gespeichert wird.
Fügen Sie nun die Instanz dieser Klasse als statische Eigenschaft für die Komponente hinzu.
<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>
Und im Komponentencode abonnieren wir die Wertänderungsmethode
updateLabel () , zu der wir die
Wertanzeige des globalen gemeinsam genutzten Zählers hinzufügen. Und im Click-Handler ein direkter Aufruf der inkrementellen Methode.
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(); } }
Trotz der Tatsache, dass jedes Element zwei Zähler erhalten hat, wird jeder seiner Werte unabhängig erhöht, aber der Wert des gemeinsam genutzten Zählers ist für beide Elemente gleich, und die einzelnen Werte werden durch die Anzahl der Klicks nur auf diese bestimmt.

Daher haben wir eine direkte Bindung in der direkten Bindung erhalten, und aufgrund der Verwendung von Ereignissen ist dieser Wert bei der Aktualisierung schwach. Natürlich hindert nichts die Implementierung des Inkrements durch Ereignisse, indem die Methode
increment () an den Lyser des Ereignisses gebunden wird:
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 } })); } }
und Ersetzen des Methodenaufrufs durch einen Ereignisauswurf:
export class MyWebComp extends HTMLElement { ... showMessage(event) { this.setAttribute('count', parseInt(this.getAttribute('count')) + 1); this.counter.dispatchEvent(new CustomEvent('increment')); } }
Was ändert sich daran? Wenn nun während der Entwicklung die Methode
increment () entfernt oder geändert wird, wird die Richtigkeit unseres Codes verletzt, es treten jedoch keine Interpreterfehler auf, d. H. Die Bedienbarkeit bleibt erhalten. Diese Eigenschaft wird als schwache Konnektivität bezeichnet.
In der Entwicklung sind sowohl direkte als auch schwache Bindungen erforderlich. In der Regel ist es üblich, eine direkte Bindung innerhalb der Logik eines Komponentenmoduls und eine schwache Kopplung zwischen verschiedenen Modulen und Komponenten zu implementieren, um Flexibilität und Erweiterbarkeit des gesamten Systems bereitzustellen. HTML-Semantik impliziert ein hohes Maß an solcher Flexibilität, d.h. Bevorzugung einer schwachen Konnektivität, aber die Anwendung funktioniert zuverlässiger, wenn die Verbindungen innerhalb der Komponenten direkt sind.
Wir können Handler auf die alte Art und Weise auflegen und Ereignisse für das globale
Dokumentobjekt aufrufen.
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 } })); } }
Das Verhalten ist nicht anders, aber jetzt müssen wir darüber nachdenken, den Ereignishandler aus dem globalen Objekt zu entfernen, wenn das Element selbst aus dem Baum entfernt wird. Glücklicherweise bieten uns Webkomponenten nicht nur Design-Hooks, sondern auch Zerstörung, wodurch Speicherlecks vermieden werden.
Es gibt noch einen weiteren wichtigen Punkt: Die Ereignisse von Baumelementen können über einen Mechanismus, der als „Blasenbildung“ bezeichnet wird, miteinander interagieren.
Wenn der Benutzer auf unser Element klickt, wird das Ereignis, das am Element selbst gearbeitet hat, auf dem übergeordneten Element wie Kreise auf dem Wasser, dann auf dem übergeordneten Element dieses übergeordneten Elements usw. zum Stammelement aufgerufen.
Zu jedem Zeitpunkt in dieser Aufrufkette kann das Ereignis abgefangen und verarbeitet werden.
Dies ist natürlich nicht ganz dasselbe Ereignis, und seine Ableitungen und Kontexte, obwohl sie Verknüpfungen miteinander enthalten, wie zum Beispiel in
event.path , werden nicht vollständig zusammenfallen.
Mit diesem Mechanismus können Sie jedoch untergeordnete Elemente so mit ihren Eltern verknüpfen, dass diese Verknüpfung nicht gegen technische Muster verstößt, d. H. ohne direkte Links, aber nur aufgrund ihres eigenen Verhaltens.
In der Antike verursachte dies auch große Probleme, als die Ereignisse einiger Elemente in einigen Abschnitten des Dokuments „Kreise“ oder Echos verursachten, die für Reaktionen unerwünscht waren.
Jahrelang kämpften Webentwickler mit der „Propaganda“ (Ausbreitung oder Auftauchen) von Ereignissen und stoppten sie und drehten sie so, wie sie konnten, aber für ihre bequeme Verwendung, wie im Fall von IDs, fehlte nur die Isolierung des Schattenbaums.
Die Magie hier ist, dass Ereignisse nicht außerhalb des Schattenbaums plündern. Sie können das von den untergeordneten Elementen in der Host-Komponente ausgelöste Ereignis jedoch abfangen und in Form einer anderen Ereignisbedeutung im Baum zusammenrollen oder es einfach verarbeiten.
Um zu verstehen, wie das alles funktioniert, verpacken wir unsere
Webcomp- Elemente in einen Container wie
folgt :
<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>
Der Container hat diesen 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); }); } } }
In
ConnectedCallback () hängen wir den Handler an das
Inkrement- Ereignis, das die untergeordneten Elemente auslöst. Der Handler erhöht den eigenen Zähler des Elements, und ein Rückruf zum Ändern seines Werts durchläuft alle untergeordneten Elemente und erhöht ihre Zähler, an denen die zuvor von uns entwickelten Handler hängen.
Der Code der untergeordneten Elemente ändert sich geringfügig. Tatsächlich muss das
Inkrementierungsereignis lediglich das Element selbst und nicht seine Aggregate löschen und dies mit dem Attribut
bubbles: true tun.
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')); } }

Trotz einiger allgemeiner Zufälligkeit zeigen die ersten Elementzähler immer den Wert des übergeordneten Elements an. Alles in allem ist das, was wir heute hier getan haben, sicherlich kein Beispiel, sondern nur ein Überblick über die Möglichkeiten und Techniken, mit denen Sie eine wirklich modulare Interaktion zwischen Komponenten organisieren können, wobei ein Minimum an Werkzeugen vermieden wird.
Den fertigen Code für die Referenz finden Sie im selben Repository beim
Ereignis-Brunch .