Erfahren Sie mehr über die Vorteile der Verwendung von Webkomponenten, wie sie funktionieren und wie Sie beginnen.
Mit Webkomponenten (im Folgenden als Komponenten bezeichnet) können Entwickler ihre eigenen HTML-Elemente erstellen. In diesem Handbuch erfahren Sie alles, was Sie über die Komponenten wissen müssen. Wir beginnen damit, was die Komponenten sind, welche Vorteile sie haben und woraus sie bestehen.
Danach beginnen wir mit dem Erstellen der Komponenten, indem wir zuerst HTML-Vorlagen und die Schatten-DOM-Oberfläche verwenden, dann ein wenig in das Thema eintauchen und sehen, wie ein benutzerdefiniertes integriertes Element erstellt wird.
Was sind Komponenten?
Entwickler lieben Komponenten (hier meinen wir die Implementierung des Entwurfsmoduls "Modul"). Dies ist eine großartige Möglichkeit, einen Codeblock zu definieren, der jederzeit und überall verwendet werden kann. Im Laufe der Jahre wurden mehrere mehr oder weniger erfolgreiche Versuche unternommen, diese Idee in die Praxis umzusetzen.
Mozillas XML-Bindungssprache und Microsofts HTML-Komponentenspezifikation für Internet Explorer 5 waren vor etwa 20 Jahren. Leider waren beide Implementierungen sehr komplex und interessierten die Hersteller anderer Browser nicht und wurden daher bald vergessen. Trotzdem haben sie den Grundstein für das gelegt, was wir heute in diesem Bereich haben.
JavaScript-Frameworks wie React, Vue und Angular verfolgen einen ähnlichen Ansatz. Einer der Hauptgründe für ihren Erfolg ist die Möglichkeit, die allgemeine Logik der Anwendung in einige Vorlagen zu kapseln, die sich leicht von einem Formular in ein anderes verschieben lassen.
Während diese Frameworks das Entwicklungserlebnis verbessern, hat alles seinen Preis. Sprachfunktionen wie JSX müssen kompiliert werden, und die meisten Frameworks verwenden eine JavaScript-Engine, um ihre Abstraktionen zu verwalten. Gibt es einen anderen Ansatz zur Lösung des Problems der Aufteilung von Code in Komponenten? Die Antwort sind Webkomponenten.
4 Säulen von Komponenten
Komponenten bestehen aus drei APIs - benutzerdefinierten Elementen, HTML-Vorlagen und Schatten-DOM sowie den zugrunde liegenden JavaScript-Modulen (ES6-Module). Mit den von diesen Schnittstellen bereitgestellten Tools können Sie benutzerdefinierte HTML-Elemente erstellen, die sich wie ihre nativen Gegenstücke verhalten.
Komponenten werden wie normale HTML-Elemente verwendet. Sie können mithilfe von Attributen angepasst, mit JavaScript abgerufen und mit CSS gestaltet werden. Die Hauptsache ist, den Browser darüber zu informieren, dass sie existieren.
Dadurch können Komponenten mit anderen Frameworks und Bibliotheken interagieren. Durch die Verwendung des gleichen Kommunikationsmechanismus wie reguläre Elemente können sie von jedem vorhandenen Framework sowie von Tools verwendet werden, die in Zukunft erscheinen werden.
Es ist auch zu beachten, dass die Komponenten den Webstandards entsprechen. Das Web basiert auf der Idee der Abwärtskompatibilität. Dies bedeutet, dass die heute erstellten Komponenten lange Zeit hervorragend funktionieren werden.
Schauen wir uns jede Spezifikation einzeln an.
1. Benutzerdefinierte Elemente
Hauptmerkmale:
- Elementverhalten definieren
- Auf Attributänderungen reagieren
- Vorhandene Elemente erweitern
Wenn Menschen über Komponenten sprechen, meinen sie oft die Schnittstelle von benutzerdefinierten Elementen.
Mit dieser API können Sie Elemente erweitern, indem Sie ihr Verhalten beim Hinzufügen, Aktualisieren und Entfernen definieren.
class ExampleElement extends HTMLElement {
static get observedAttributes() {
return [...]
}
attributeChangedCallback(name, oldValue, newValue) {}
connectedCallback() {}
}
customElements.define('example-element', ExampleElement)
Jedes benutzerdefinierte Element hat eine ähnliche Struktur. Es erweitert die Funktionalität der vorhandenen HTMLElements-Klasse.
Innerhalb eines benutzerdefinierten Elements gibt es verschiedene Methoden, die als Reaktionen bezeichnet werden und für die Behandlung einer bestimmten Änderung an einem Element verantwortlich sind. Beispielsweise wird connectCallback aufgerufen, wenn der Seite ein Element hinzugefügt wird. Dies ähnelt den in Frameworks verwendeten Lebenszyklusphasen (componentDidMount in React, in Vue bereitgestellt).
Das Ändern der Attribute eines Elements führt zu einer Änderung seines Verhaltens. Bei einer Aktualisierung wird das attributeChangedCallback aufgerufen, das Informationen zur Änderung enthält. Dies geschieht nur für die Attribute, die in dem von ObservatedAttributes zurückgegebenen Array angegeben sind.
Das Element muss definiert werden, bevor der Browser es verwenden kann. Die "define" -Methode akzeptiert zwei Argumente - den Namen des Tags und seine Klasse. Alle Tags müssen das Zeichen "-" enthalten, um Konflikte mit vorhandenen und zukünftigen nativen Elementen zu vermeiden.
<example-element>Content</example-element>
Das Element kann wie ein normales HTML-Tag verwendet werden. Wenn ein solches Element gefunden wird, ordnet der Browser sein Verhalten der angegebenen Klasse zu. Dieser Vorgang wird als "Upgrade" bezeichnet.
Es gibt zwei Arten von Elementen: "Autonom" und "Customized Build-In". Bisher haben wir uns eigenständige Artikel angesehen. Dies sind Elemente, die sich nicht auf vorhandene HTML-Elemente beziehen. Wie die div- und span-Tags, die keine spezifische semantische Bedeutung haben.
Benutzerdefinierte Inline-Elemente erweitern - wie der Name schon sagt - die Funktionalität vorhandener HTML-Elemente. Sie erben das semantische Verhalten dieser Elemente und können es ändern. Wenn beispielsweise das Element "Eingabe" angepasst wurde, bleibt es beim Senden weiterhin ein Eingabefeld und Teil des Formulars.
class CustomInput extends HTMLInputElement {}
customElements.define('custom-input', CustomInput, { extends: 'input' })
Die benutzerdefinierte Inline-Elementklasse erweitert die benutzerdefinierte Elementklasse. Beim Definieren eines Inline-Elements wird das erweiterbare Element als drittes Argument übergeben.
<input is="custom-input" />
Die Verwendung des Tags unterscheidet sich ebenfalls geringfügig. Anstelle eines neuen Tags wird das vorhandene verwendet, wobei das spezielle Erweiterungsattribut "is" angegeben wird. Wenn der Browser auf dieses Attribut stößt, weiß er, dass es sich um ein benutzerdefiniertes Element handelt, und aktualisiert es entsprechend.
Während eigenständige Elemente von den meisten modernen Browsern unterstützt werden, werden benutzerdefinierte Inline-Elemente nur von Chrome und Firefox unterstützt. Wenn sie in einem Browser verwendet werden, der sie nicht unterstützt, werden letztere als reguläre HTML-Elemente behandelt, sodass sie im Großen und Ganzen auch in diesen Browsern sicher verwendet werden können.
2. HTML-Vorlagen
- Erstellung von vorgefertigten Strukturen
- Werden vor dem Anruf nicht auf der Seite angezeigt
- Enthalten HTML, CSS und JS
In der Vergangenheit umfasste das clientseitige Templating das Verketten von Zeichenfolgen in JavaScript oder die Verwendung von Bibliotheken wie Lenker, um Blöcke mit benutzerdefinierten Markups zu analysieren. Vor kurzem hat die Spezifikation ein "Template" -Tag, das alles enthalten kann, was wir verwenden möchten.
<template id="tweet">
<div class="tweet">
<span class="message"></span>
Written by @
<span class="username"></span>
</div>
</template>
An sich hat dies keine Auswirkungen auf die Seite, d. H. Es wird nicht von der Engine analysiert, es werden keine Ressourcenanforderungen (Audio, Video) gesendet. JavaScript kann nicht darauf zugreifen, und für Browser ist es ein leeres Element.
const template = document.getElementById('tweet')
const node = document.importNode(template.content, true)
document.body.append(node)
Zuerst erhalten wir das Element "Vorlage". Die importNode-Methode erstellt eine Kopie ihres Inhalts, das zweite Argument (true) bedeutet Deep Copy. Schließlich fügen wir es der Seite wie jedes andere Element hinzu.
Vorlagen können alles enthalten, was normales HTML enthalten kann, einschließlich CSS und JavaScript. Wenn der Seite ein Element hinzugefügt wird, werden Stile darauf angewendet und Skripte gestartet. Denken Sie daran, dass Stile und Skripte global sind. Dies bedeutet, dass sie andere Stile und Werte überschreiben können, die von Skripten verwendet werden.
Die Vorlagen sind nicht darauf beschränkt. Sie erscheinen in ihrer ganzen Pracht, wenn sie mit anderen Teilen der Komponenten verwendet werden, insbesondere mit dem Schatten-DOM.
3. Shadow DOM
- Vermeidet Stilkonflikte
- Es wird einfacher, Namen zu finden (z. B. von Klassen).
- Kapselung der Implementierungslogik
Mit dem Document Object Model (DOM) interpretiert der Browser die Struktur der Seite. Durch Lesen des Markups bestimmt der Browser, welche Elemente welchen Inhalt enthalten, und entscheidet auf dieser Grundlage, was auf der Seite angezeigt werden soll. Wenn Sie beispielsweise document.getElemetById () verwenden, greift der Browser auf das DOM zu, um das benötigte Element zu finden.
Für ein Seitenlayout ist dies in Ordnung, aber was ist mit den Details, die im Element versteckt sind? Zum Beispiel sollte es einer Seite egal sein, welche Schnittstelle im "Video" -Element enthalten ist. Hier bietet sich Shadow DOM an.
<div id="shadow-root"></div>
<script>
const host = document.getElementById('shadow-root')
const shadow = host.attachShadow({ mode: 'open' })
</script>
Das Schatten-DOM wird erstellt, wenn es auf ein Element angewendet wird. Jeder Inhalt kann dem Schatten-DOM hinzugefügt werden, genau wie ein normales ("helles") DOM. Das Schatten-DOM wird nicht von dem beeinflusst, was draußen passiert, d. H. außerhalb davon. Normales DOM kann auch nicht direkt auf Schatten zugreifen. Dies bedeutet, dass wir im Schatten-DOM beliebige Klassennamen, Stile und Skripte verwenden können und uns nicht um mögliche Konflikte sorgen müssen.
Die besten Ergebnisse werden mit Shadow DOM in Verbindung mit benutzerdefinierten Elementen erzielt. Dank des Schatten-DOM wirken sich Stile und Strukturen einer Komponente bei Wiederverwendung in keiner Weise auf andere Elemente auf der Seite aus.
ES- und HTML-Module
- Nach Bedarf hinzufügen
- Keine Vorgenerierung erforderlich
- Alles ist an einem Ort gespeichert
Während die vorherigen drei Spezifikationen einen langen Weg in ihrer Entwicklung zurückgelegt haben, bleibt die Art und Weise, wie sie verpackt und wiederverwendet werden, Gegenstand intensiver Debatten.
Die HTML-Importspezifikation definiert, wie HTML-Dokumente sowie CSS und JavaScript exportiert und importiert werden. Auf diese Weise können benutzerdefinierte Elemente zusammen mit Vorlagen und Schatten-DOM an anderer Stelle platziert und nach Bedarf verwendet werden.
Firefox lehnte es jedoch ab, diese Spezifikation in seinem Browser zu implementieren, und bot eine andere Möglichkeit an, die auf JavaScript-Modulen basiert.
export class ExampleElement external HTMLElement {}
import { ExampleElement } from 'ExampleElement.js'
Module haben standardmäßig einen eigenen Namespace, d. H. Ihr Inhalt ist nicht global. Exportierte Variablen, Funktionen und Klassen können überall und jederzeit importiert und als lokale Ressourcen verwendet werden.
Dies funktioniert hervorragend für Komponenten. Benutzerdefinierte Elemente, die Vorlagen- und Schatten-DOM enthalten, können aus einer Datei exportiert und in einer anderen verwendet werden.
import { ExampleElement } from 'ExampleElement.html'
Microsoft hat einen Vorschlag zur Erweiterung der JavaScript-Modulspezifikation um HTML-Export / -Import vorgelegt. Auf diese Weise können Sie Komponenten mit deklarativem und semantischem HTML erstellen. Diese Funktion wird bald in Chrome und Edge verfügbar sein.
Eigene Komponente erstellen
Während es viele Dinge an Komponenten gibt, die Sie möglicherweise als komplex empfinden, sind für das Erstellen und Verwenden einer einfachen Komponente nur wenige Codezeilen erforderlich. Schauen wir uns einige Beispiele an.
Mit Komponenten können Sie Benutzerkommentare mithilfe von HTML-Vorlagen und Schatten-DOM-Schnittstellen anzeigen.
Erstellen wir eine Komponente zum Anzeigen von Benutzerkommentaren mithilfe von HTML-Vorlagen und Schatten-DOM.
1. Erstellen einer Vorlage
Die Komponente benötigt eine Kopie zum Kopieren, bevor das Markup generiert wird. Die Vorlage kann sich an einer beliebigen Stelle auf der Seite befinden. Die benutzerdefinierte Elementklasse kann über die ID darauf zugreifen.
Fügen Sie der Seite das Element "Vorlage" hinzu. Alle für dieses Element definierten Stile wirken sich nur darauf aus.
<template id="user-comment-template">
<style>
...
</style>
</template>
2. Markup hinzufügen
Neben Stilen kann eine Komponente ein Layout (Struktur) enthalten. Zu diesem Zweck wird das Element "div" verwendet.
Dynamischer Inhalt wird durch Slots geleitet. Fügen Sie Slots für den Avatar, den Namen und die Benutzernachricht mit den entsprechenden Attributen "Name" hinzu:
<div class="container">
<div class="avatar-container">
<slot name="avatar"></slot>
</div>
<div class="comment">
<slot name="username"></slot>
<slot name="comment"></slot>
</div>
</div>
Standard-Slot-Inhalt
Der Standardinhalt wird angezeigt, wenn keine Informationen an den Steckplatz übergeben wurden.
Daten, die an den Steckplatz übergeben wurden, überschreiben die Daten in der Vorlage. Wenn keine Informationen an den Steckplatz übergeben werden, wird der Standardinhalt angezeigt.
In diesem Fall wird an seiner Stelle die Meldung "Kein Name" angezeigt, wenn der Benutzername nicht übertragen wurde:
<slot name="username">
<span class="unknown">No name</span>
</slot>
3. Eine Klasse erstellen
Das Erstellen eines benutzerdefinierten Elements beginnt mit der Erweiterung der HTMLElement-Klasse. Ein Teil des Setup-Prozesses besteht darin, eine Schattenwurzel zu erstellen, um den Inhalt des Elements zu rendern. Wir öffnen es für den Zugriff im nächsten Schritt.
Schließlich informieren wir den Browser über die neue UserComment-Klasse.
class UserComment extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
}
}
customElements.define('user-comment', UserComment)
4. Anwenden von Schatteninhalten
Wenn der Browser auf das Element "Benutzerkommentar" stößt, überprüft er den Schattenstammknoten, um dessen Inhalt abzurufen. Das zweite Argument weist den Browser an, den gesamten Inhalt zu kopieren, nicht nur die erste Ebene (Elemente der obersten Ebene).
Fügen Sie dem Schattenstammknoten ein Markup hinzu, das das Erscheinungsbild der Komponente sofort aktualisiert.
connectedCallback() {
const template = document.getElementById('user-comment-template')
const node = document.importNode(template.content, true)
this.shadowRoot.append(node)
}
5. Verwenden der Komponente
Die Komponente ist jetzt einsatzbereit. Fügen Sie das Tag "Benutzerkommentar" hinzu und übergeben Sie die erforderlichen Informationen.
Da alle Slots Namen haben, wird alles, was außerhalb von ihnen übergeben wird, ignoriert. Alles in den Slots wird genau wie übergeben kopiert, einschließlich des Stils.
<user-comment>
<img alt="" slot="avatar" src="avatar.png" />
<span slot="username">Matt Crouch</span>
<div slot="comment">This is an example of a comment</div>
</user-comment>
Erweiterter Beispielcode:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Components Example</title>
<style>
body {
display: grid;
place-items: center;
}
img {
width: 80px;
border-radius: 4px;
}
</style>
</head>
<body>
<template id="user-comment-template">
<div class="container">
<div class="avatar-container">
<slot name="avatar">
<slot class="unknown"></slot>
</slot>
</div>
<div class="comment">
<slot name="username">No name</slot>
<slot name="comment"></slot>
</div>
</div>
<style>
.container {
width: 320px;
clear: both;
margin-bottom: 1rem;
}
.avatar-container {
float: left;
margin-right: 1rem;
}
.comment {
height: 80px;
display: flex;
flex-direction: column;
justify-content: center;
}
.unknown {
display: block;
width: 80px;
height: 80px;
border-radius: 4px;
background: #ccc;
}
</style>
</template>
<user-comment>
<img alt="" slot="avatar" src="avatar1.jpg" />
<span slot="username">Matt Crouch</span>
<div slot="comment">Fisrt comment</div>
</user-comment>
<user-comment>
<img alt="" slot="avatar" src="avatar2.jpg" />
<!-- no username -->
<div slot="comment">Second comment</div>
</user-comment>
<user-comment>
<!-- no avatar -->
<span slot="username">John Smith</span>
<div slot="comment">Second comment</div>
</user-comment>
<script>
class UserComment extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
}
connectedCallback() {
const template = document.getElementById("user-comment-template");
const node = document.importNode(template.content, true);
this.shadowRoot.append(node);
}
}
customElements.define("user-comment", UserComment);
</script>
</body>
</html>
Erstellen eines benutzerdefinierten Inline-Elements
Wie bereits erwähnt, können benutzerdefinierte Elemente vorhandene Elemente erweitern. Dies spart Zeit, indem das Standardverhalten des vom Brutapparat bereitgestellten Elements beibehalten wird. In diesem Abschnitt sehen wir uns an, wie Sie das Element "Zeit" erweitern können.
1. Eine Klasse erstellen
Integrierte Elemente, wie eigenständige, werden angezeigt, wenn die Klasse erweitert wird. Anstelle der allgemeinen Klasse "HTMLElement" erweitern sie jedoch eine bestimmte Klasse.
In unserem Fall ist diese Klasse HTMLTimeElement - die Klasse, die von den "Zeit" -Elementen verwendet wird. Es enthält Verhalten in Bezug auf das Attribut "datetime", einschließlich des Datenformats.
class RelativeTime extends HTMLTimeElement {}
2. Elementdefinition
Das Element wird vom Browser mit der Methode "define" registriert. Im Gegensatz zu einem eigenständigen Element muss der "define" -Methode beim Registrieren eines Inline-Elements jedoch ein drittes Argument übergeben werden, ein Objekt mit Einstellungen.
Unser Objekt enthält einen Schlüssel mit dem Wert des benutzerdefinierten Elements. Es nimmt den Namen des Tags an. Wenn ein solcher Schlüssel nicht vorhanden ist, wird eine Ausnahme ausgelöst.
customElements.define('relative-time', RelativeTime, { extends: 'time' })
3. Uhrzeit einstellen
Da eine Seite mehrere Komponenten enthalten kann, muss die Komponente eine Methode zum Festlegen des Werts eines Elements bereitstellen. Innerhalb dieser Methode übergibt die Komponente einen Zeitwert an die Bibliothek "timeago" und legt den von dieser Bibliothek zurückgegebenen Wert als Elementwert fest (Entschuldigung für die Tautologie).
Schließlich setzen wir das title-Attribut so, dass der Benutzer den beim Schweben festgelegten Wert sehen kann.
setTime() {
this.innerHTML = timeago().format(this.getAttribute('datetime'))
this.setAttribute('title', this.getAttribute('datetime'))
}
4. Verbindungsaktualisierung
Die Komponente kann die Methode unmittelbar nach der Anzeige auf der Seite verwenden. Da Inline-Komponenten kein Schatten-DOM haben, benötigen sie keinen Konstruktor.
connectedCAllback() {
this.setTime()
}
5. Verfolgen der Änderung von Attributen
Wenn Sie die Uhrzeit programmgesteuert aktualisieren, reagiert die Komponente nicht. Er weiß nicht, dass er auf Änderungen im Attribut "datetime" achten muss.
Sobald beobachtete Attribute definiert wurden, wird attributeChangedCallback aufgerufen, wenn sie sich ändern.
static get observedAttributes() {
return ['datetime']
}
attributeChangedCallback() {
this.setTime()
}
6. Hinzufügen zur Seite
Da unser Element eine Erweiterung des nativen Elements ist, unterscheidet sich seine Implementierung geringfügig. Um es zu verwenden, fügen Sie der Seite das Tag "time" mit einem speziellen Attribut "is" hinzu, dessen Wert der Name des bei der Registrierung definierten integrierten Elements ist. Browser, die keine Komponenten unterstützen, rendern Fallback-Inhalte.
<time is="relative-time" datetime="2020-09-20T12:00:00+0000">
20 2020 . 12:00
</time>
Erweiterter Beispielcode:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Components Another Example</title>
<!-- timeago.js -->
<script
src="https://cdnjs.cloudflare.com/ajax/libs/timeago.js/4.0.2/timeago.min.js"
integrity="sha512-SVDh1zH5N9ChofSlNAK43lcNS7lWze6DTVx1JCXH1Tmno+0/1jMpdbR8YDgDUfcUrPp1xyE53G42GFrcM0CMVg=="
crossorigin="anonymous"
></script>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
}
input,
button {
margin-bottom: 0.5rem;
}
time {
font-size: 2rem;
}
</style>
</head>
<body>
<input type="text" placeholder="2020-10-20" value="2020-08-19" />
<button>Set Time</button>
<time is="relative-time" datetime="2020-09-19">
19 2020 .
</time>
<script>
class RelativeTime extends HTMLTimeElement {
setTime() {
this.innerHTML = timeago.format(this.getAttribute("datetime"));
this.setAttribute("title", this.getAttribute("datetime"));
}
connectedCallback() {
this.setTime();
}
static get observedAttributes() {
return ["datetime"];
}
attributeChangedCallback() {
this.setTime();
}
}
customElements.define("relative-time", RelativeTime, { extends: "time" });
const button = document.querySelector("button");
const input = document.querySelector("input");
const time = document.querySelector("time");
button.onclick = () => {
const { value } = input;
time.setAttribute("datetime", value);
};
</script>
</body>
</html>
Ich hoffe, ich habe Ihnen dabei geholfen, ein grundlegendes Verständnis dafür zu erlangen, was Webkomponenten sind, wofür sie bestimmt sind und wie sie verwendet werden.