Ein weiterer Aspekt von Web Components, über den wir noch nicht gesprochen haben, ist, dass eine JavaScript-Funktion aufgerufen wird, wann immer eine Web Component zu einer Seite hinzugefügt oder von ihr entfernt wird. Diese Lifecycle-Callbacks können für viele Dinge genutzt werden, einschließlich der Anpassung eines Elements an seinen Kontext.
Artikelreihe
- Web Components sind einfacher als man denkt
- Interaktive Web Components sind einfacher als man denkt
- Die Verwendung von Web Components in WordPress ist einfacher als man denkt
- Standard-Elemente mit Web Components aufpeppen „ist“ einfacher als man denkt
- Kontextbezogene Web Components sind einfacher als du denkst (Du bist hier!)
- Web Component Pseudo-Klassen und Pseudo-Elemente sind einfacher als man denkt
Die vier Lifecycle-Callbacks von Web Components
Es gibt vier Lifecycle-Callbacks, die mit Web Components verwendet werden können.
connectedCallback: Dieser Callback wird ausgelöst, wenn das benutzerdefinierte Element an das DOM *angehängt* wird.disconnectedCallback: Dieser Callback wird ausgelöst, wenn das Element aus dem Dokument *entfernt* wird.adoptedCallback: Dieser Callback wird ausgelöst, wenn das Element in ein neues Dokument *verschoben* wird.attributeChangedCallback: Dieser Callback wird ausgelöst, wenn ein *Attribut* geändert, hinzugefügt oder entfernt wird, solange dieses Attribut beobachtet wird.
Betrachten wir jeden dieser Fälle in Aktion.
Unsere postapokalyptische Personenkomponente

Wir beginnen mit der Erstellung einer Web Component namens <postapocalyptic-person>. Jede Person nach der Apokalypse ist entweder ein Mensch oder ein Zombie, und wir werden das anhand einer Klasse — entweder .human oder .zombie — erkennen, die auf das Elternelement der <postapocalyptic-person> Komponente angewendet wird. Wir werden damit noch nichts Besonderes machen (noch nicht), aber wir fügen einen shadowRoot hinzu, den wir verwenden können, um ein entsprechendes Bild basierend auf dieser Klassifizierung anzuhängen.
customElements.define(
"postapocalyptic-person",
class extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "open" });
}
}
Unser HTML sieht so aus
<div class="humans">
<postapocalyptic-person></postapocalyptic-person>
</div>
<div class="zombies">
<postapocalyptic-person></postapocalyptic-person>
</div>
Personen mit connectedCallback einfügen
Wenn eine <postapocalyptic-person> auf der Seite geladen wird, wird die Funktion connectedCallback() aufgerufen.
connectedCallback() {
let image = document.createElement("img");
if (this.parentNode.classList.contains("humans")) {
image.src = "https://assets.codepen.io/1804713/lady.png";
this.shadowRoot.appendChild(image);
} else if (this.parentNode.classList.contains("zombies")) {
image.src = "https://assets.codepen.io/1804713/ladyz.png";
this.shadowRoot.appendChild(image);
}
}
Dies stellt sicher, dass ein Bild eines Menschen ausgegeben wird, wenn die <postapocalyptic-person> ein Mensch ist, und ein Zombie-Bild, wenn die Komponente ein Zombie ist.
Seien Sie vorsichtig bei der Arbeit mit connectedCallback. Es wird häufiger ausgeführt, als Sie vielleicht denken, und wird jedes Mal ausgelöst, wenn das Element verschoben wird, und kann (verblüffenderweise) auch ausgeführt werden, *nachdem* der Knoten nicht mehr verbunden ist – was zu erheblichen Leistungseinbußen führen kann. Sie können this.isConnected verwenden, um zu prüfen, ob das Element verbunden ist oder nicht.
Zählen von Personen mit connectedCallback(), wenn sie hinzugefügt werden
Wir werden es etwas komplexer machen, indem wir ein paar Buttons hinzufügen. Einer wird eine <postapocalyptic-person> hinzufügen, wobei ein „Münzwurf“-Ansatz bestimmt, ob es sich um einen Menschen oder einen Zombie handelt. Der andere Button tut das Gegenteil und entfernt zufällig eine <postapocalyptic-person>. Wir werden dabei verfolgen, wie viele Menschen und Zombies sichtbar sind.
<div class="btns">
<button id="addbtn">Add Person</button>
<button id="rmvbtn">Remove Person</button>
<span class="counts">
Humans: <span id="human-count">0</span>
Zombies: <span id="zombie-count">0</span>
</span>
</div>
Das machen unsere Buttons
let zombienest = document.querySelector(".zombies"),
humancamp = document.querySelector(".humans");
document.getElementById("addbtn").addEventListener("click", function () {
// Flips a "coin" and adds either a zombie or a human
if (Math.random() > 0.5) {
zombienest.appendChild(document.createElement("postapocalyptic-person"));
} else {
humancamp.appendChild(document.createElement("postapocalyptic-person"));
}
});
document.getElementById("rmvbtn").addEventListener("click", function () {
// Flips a "coin" and removes either a zombie or a human
// A console message is logged if no more are available to remove.
if (Math.random() > 0.5) {
if (zombienest.lastElementChild) {
zombienest.lastElementChild.remove();
} else {
console.log("No more zombies to remove");
}
} else {
if (humancamp.lastElementChild) {
humancamp.lastElementChild.remove();
} else {
console.log("No more humans to remove");
}
}
});
Hier ist der Code in connectedCallback(), der die Menschen und Zombies zählt, wenn sie hinzugefügt werden.
connectedCallback() {
let image = document.createElement("img");
if (this.parentNode.classList.contains("humans")) {
image.src = "https://assets.codepen.io/1804713/lady.png";
this.shadowRoot.appendChild(image);
// Get the existing human count.
let humancount = document.getElementById("human-count");
// Increment it
humancount.innerHTML = parseInt(humancount.textContent) + 1;
} else if (this.parentNode.classList.contains("zombies")) {
image.src = "https://assets.codepen.io/1804713/ladyz.png";
this.shadowRoot.appendChild(image);
// Get the existing zombie count.
let zombiecount = document.getElementById("zombie-count");
// Increment it
zombiecount.innerHTML = parseInt(zombiecount.textContent) + 1;
}
}
Aktualisierung der Zählungen mit disconnectedCallback
Als nächstes können wir disconnectedCallback() verwenden, um die Anzahl der Menschen und Zombies zu dekrementieren, wenn sie entfernt werden. Allerdings können wir die Klasse des Elternelements nicht überprüfen, da das Elternelement mit der entsprechenden Klasse bereits weg ist, wenn disconnectedCallback aufgerufen wird. Wir könnten ein Attribut am Element setzen oder eine Eigenschaft zum Objekt hinzufügen, aber da das src-Attribut des Bildes bereits durch sein Elternelement bestimmt wird, können wir dies als Stellvertreter verwenden, um zu wissen, ob die entfernte Web Component ein Mensch oder ein Zombie ist.
disconnectedCallback() {
let image = this.shadowRoot.querySelector('img');
// Test for the human image
if (image.src == "https://assets.codepen.io/1804713/lady.png") {
let humancount = document.getElementById("human-count");
humancount.innerHTML = parseInt(humancount.textContent) - 1; // Decrement count
// Test for the zombie image
} else if (image.src == "https://assets.codepen.io/1804713/ladyz.png") {
let zombiecount = document.getElementById("zombie-count");
zombiecount.innerHTML = parseInt(zombiecount.textContent) - 1; // Decrement count
}
}
Vorsicht vor Clowns!
Jetzt (und ich spreche aus Erfahrung, natürlich) ist das Einzige, was beängstigender ist als eine Horde Zombies, die auf Ihre Position zustürmt, ein Clown – es braucht nur einen! Also, auch wenn wir uns bereits mit beängstigenden postapokalyptischen Zombies befassen, fügen wir die Möglichkeit hinzu, dass ein Clown die Szene betritt, um noch mehr Schrecken zu verbreiten. Tatsächlich werden wir dies so tun, dass jede Person oder jeder Zombie auf dem Bildschirm heimlich ein Clown in Verkleidung sein kann!
Ich nehme meine frühere Aussage zurück: Ein einzelner Zombie-Clown ist beängstigender als selbst eine Gruppe „normaler“ Clowns. Nehmen wir an, wenn irgendeine Art von Clown gefunden wird – sei es ein Mensch oder ein Zombie –, trennen wir sie von der Menschen- und Zombiepopulation, indem wir sie in ein ganz anderes Dokument schicken – eine <iframe>-Gefängnis, wenn Sie so wollen. (Ich höre, „clowning“ mag noch ansteckender sein als Zombie-Kontagion.)
Und wenn wir einen vermuteten Clown vom aktuellen Dokument in ein <iframe> verschieben, zerstört und erstellt es nicht den ursprünglichen Knoten neu; stattdessen übernimmt es den besagten Knoten und verbindet ihn, ruft zuerst adoptedCallback und dann connectedCallback auf.
Wir brauchen nichts im <iframe>-Dokument außer einem Body mit einer .clowns-Klasse. Solange dieses Dokument im Iframe des Hauptdokuments enthalten ist – nicht separat angezeigt wird –, benötigen wir nicht einmal den <postapocalyptic-person>-Instanziierungscode. Wir fügen einen Platz für Menschen, einen weiteren Platz für Zombies und ja, das Clown-Gefängnis... äh... <iframe> des... Spaßes ein.
<div class="btns">
<button id="addbtn">Add Person</button>
<button id="jailbtn">Jail Potential Clown</button>
</div>
<div class="humans">
<postapocalyptic-person></postapocalyptic-person>
</div>
<div class="zombies">
<postapocalyptic-person></postapocalyptic-person>
</div>
<iframe class="clowniframeoffun” src="adoptedCallback-iframe.html">
</iframe>
Unser Button „Person hinzufügen“ funktioniert wie im letzten Beispiel: Er wirft eine digitale Münze, um zufällig entweder einen Menschen oder einen Zombie einzufügen. Wenn wir auf den Button „Potenziellen Clown einsperren“ klicken, wird eine weitere Münze geworfen und nimmt entweder einen Zombie oder einen Menschen auf, den sie dann in das <iframe>-Gefängnis übergibt.
document.getElementById("jailbtn").addEventListener("click", function () {
if (Math.random() > 0.5) {
let human = humancamp.querySelector('postapocalyptic-person');
if (human) {
clowncollege.contentDocument.querySelector('body').appendChild(document.adoptNode(human));
} else {
console.log("No more potential clowns at the human camp");
}
} else {
let zombie = zombienest.querySelector('postapocalyptic-person');
if (zombie) {
clowncollege.contentDocument.querySelector('body').appendChild(document.adoptNode(zombie));
} else {
console.log("No more potential clowns at the zombie nest");
}
}
});
Clowns mit adoptedCallback enthüllen
In adoptedCallback werden wir bestimmen, ob der Clown von der Zombie-Mensch-Sorte ist, basierend auf seinem entsprechenden Bild, und dann das Bild entsprechend ändern. connectedCallback wird danach aufgerufen, aber wir haben nichts, was es tun müsste, und was es tut, wird unsere Änderungen nicht beeinträchtigen. Wir können es also so belassen.
adoptedCallback() {
let image = this.shadowRoot.querySelector("img");
if (this.parentNode.dataset.type == "clowns") {
if (image.src.indexOf("lady.png") != -1) {
// Sometimes, the full URL path including the domain is saved in `image.src`.
// Using `indexOf` allows us to skip the unnecessary bits.
image.src = "ladyc.png";
this.shadowRoot.appendChild(image);
} else if (image.src.indexOf("ladyz.png") != -1) {
image.src = "ladyzc.png";
this.shadowRoot.appendChild(image);
}
}
}
Versteckte Clowns mit attributeChangedCallback erkennen
Schließlich haben wir den attributeChangedCallback. Im Gegensatz zu den drei anderen Lifecycle-Callbacks müssen wir die Attribute unserer Web Component beobachten, damit der Callback ausgelöst wird. Dies können wir tun, indem wir der Klasse des benutzerdefinierten Elements eine Funktion observedAttributes() hinzufügen und diese Funktion ein Array von Attributnamen zurückgeben lassen.
static get observedAttributes() {
return [“attribute-name”];
}
Wenn sich dieses Attribut dann ändert – einschließlich Hinzufügen oder Entfernen –, wird attributeChangedCallback ausgelöst.
Nun, das, worüber Sie sich bei Clowns Sorgen machen müssen, ist, dass einige der Menschen, die Sie kennen und lieben (oder die, die Sie kannten und liebten, bevor sie zu Zombies wurden), heimlich Clowns in Verkleidung sein könnten. Ich habe einen Clown-Detektor eingerichtet, der sich eine Gruppe von Menschen und Zombies ansieht, und wenn Sie auf den Button „Clowns enthüllen“ klicken, wendet der Detektor (auf rein wissenschaftliche und absolut vertrauenswürdige Weise, die **nicht** auf zufälligen Zahlen basiert, die einen Index wählen) data-clown="true" auf die Komponente an. Und wenn dieses Attribut angewendet wird, wird attributeChangedCallback ausgelöst und das Bild der Komponente aktualisiert, um ihre clownesken Farben zu enthüllen.
Ich sollte auch erwähnen, dass attributeChangedCallback drei Parameter entgegennimmt:
- der Name des Attributs
- der vorherige Wert des Attributs
- der neue Wert des Attributs
Darüber hinaus ermöglicht der Callback Änderungen basierend darauf, wie stark sich das Attribut geändert hat, oder basierend auf dem Übergang zwischen zwei Zuständen.
Hier ist unser attributeChangedCallback-Code
attributeChangedCallback(name, oldValue, newValue) {
let image = this.shadowRoot.querySelector("img");
// Ensures that `data-clown` was the attribute that changed,
// that its value is true, and that it had an image in its `shadowRoot`
if (name="data-clown" && this.dataset.clown && image) {
// Setting and updating the counts of humans, zombies,
// and clowns on the page
let clowncount = document.getElementById("clown-count"),
humancount = document.getElementById("human-count"),
zombiecount = document.getElementById("zombie-count");
if (image.src.indexOf("lady.png") != -1) {
image.src = "https://assets.codepen.io/1804713/ladyc.png";
this.shadowRoot.appendChild(image);
// Update counts
clowncount.innerHTML = parseInt(clowncount.textContent) + 1;
humancount.innerHTML = parseInt(humancount.textContent) - 1;
} else if (image.src.indexOf("ladyz.png") != -1) {
image.src = "https://assets.codepen.io/1804713/ladyzc.png";
this.shadowRoot.appendChild(image);
// Update counts
clowncount.innerHTML = parseInt(clowncount.textContent) + 1;
zombiecount.innerHTML = parseInt(zombiecount.textContent) - 1;
}
}
}
Und da haben Sie es! Wir haben nicht nur herausgefunden, dass Web Component Callbacks und das Erstellen von kontextbezogenen benutzerdefinierten Elementen einfacher sind, als Sie vielleicht dachten, sondern auch, dass das Erkennen von postapokalyptischen Clowns, obwohl beängstigend, ebenfalls einfacher ist, als Sie dachten. Welche Art von hinterhältigen, postapokalyptischen Clowns können Sie mit diesen Web Component Callback-Funktionen aufdecken?
Wusstest du, dass du, nachdem du HTMLElement erweitert hast, um deine benutzerdefinierte Elementklasse zu erstellen, diese Klasse erneut erweitern kannst, um eine zweite benutzerdefinierte Klasse zu erstellen? Wie üblich rufst du super() in deinem zweiten Konstruktor auf. Aber der connectedCallback() im zweiten kann etwas anderes tun als der erste, und alle anderen benutzerdefinierten Methoden sind wie erwartet Teil seines Prototyps.
Das wusste ich nicht... *fängt manisch an, Ideen aufzuschreiben*