Von der Webkomponente zum Lit-Element

1. Einführung

Zuletzt aktualisiert:10.08.2021

Webkomponenten

Webkomponenten sind eine Reihe von Webplattform-APIs, mit denen Sie neue benutzerdefinierte, wiederverwendbare und gekapselte HTML-Tags zur Verwendung auf Webseiten und Webanwendungen erstellen können. Benutzerdefinierte Komponenten und Widgets, die auf den Web Component-Standards basieren, funktionieren mit allen modernen Browsern und können mit jeder JavaScript-Bibliothek oder jedem Framework verwendet werden, das HTML unterstützt.

Was ist Lit

Lit ist eine einfache Bibliothek zum Erstellen schneller, schlanker Webkomponenten, die in jedem Framework oder ohne Framework funktionieren. Mit Lit kannst du gemeinsam nutzbare Komponenten, Anwendungen, Designsysteme und vieles mehr erstellen.

Lit stellt APIs zur Vereinfachung gängiger Webkomponenten-Aufgaben wie der Verwaltung von Eigenschaften, Attributen und Rendering bereit.

Aufgaben in diesem Lab

  • Was ist eine Webkomponente?
  • Die Konzepte der Webkomponenten
  • So erstellen Sie eine Webkomponente
  • Was sind Lit-HTML und LitElement?
  • Was Lit über eine Web-Komponente macht

Inhalt

  • Eine Vanille-Webkomponente für „Mag ich“/„Mag ich nicht“
  • Lit-based Web Component (Mag ich / Mag ich nicht)

Voraussetzungen

  • Jeder aktualisierte moderne Browser (Chrome, Safari, Firefox, Chromium Edge) Webkomponenten funktionieren in allen modernen Browsern. Polyfills sind für Microsoft Internet Explorer 11 und Microsoft Edge ohne Chromium verfügbar.
  • Kenntnisse in HTML, CSS, JavaScript und Chrome-Entwicklertools.

2. Einrichtung und Playground erkunden

Auf den Code zugreifen

Im gesamten Codelab finden Sie Links zum Lit Playground:

Der Playground ist eine Code-Sandbox, die vollständig in deinem Browser ausgeführt wird. Sie kann TypeScript- und JavaScript-Dateien kompilieren und ausführen und außerdem Importe in Knotenmodule automatisch auflösen. z.B.

// before
import './my-file.js';
import 'lit';

// after
import './my-file.js';
import 'https://unpkg.com/lit?module';

Du kannst das gesamte Tutorial im Lit Playground durchgehen und diese Checkpoints als Ausgangspunkt verwenden. Wenn Sie VS Code verwenden, können Sie diese Checkpoints nutzen, um den Startcode für einen beliebigen Schritt herunterzuladen und damit Ihre Arbeit zu überprüfen.

Erkundung der beleuchteten Spielplatz-Benutzeroberfläche

Die Registerkartenleiste der Dateiauswahl ist mit „Abschnitt 1“ beschriftet, der Abschnitt zur Codebearbeitung mit „Abschnitt 2“, die Ausgabevorschau als „Abschnitt 3“ und die Schaltfläche „Vorschau neu laden“ mit „Abschnitt 4“.

Im Screenshot der Benutzeroberfläche von Lit Playground sind die Abschnitte hervorgehoben, die Sie in diesem Codelab verwenden werden.

  1. Dateiauswahl. Beachten Sie das Pluszeichen...
  2. Dateieditor.
  3. Codevorschau.
  4. Schaltfläche „Aktualisieren“.
  5. Grafik: Symbol zum Herunterladen

VS Code-Einrichtung (erweitert)

Dies sind die Vorteile dieser VS Code-Einrichtung:

  • Prüfung des Vorlagentyps
  • Template Intellisense & automatische Vervollständigung

Wenn Sie NPM und VS Code (mit dem lit-plugin-Plug-in) bereits installiert haben und wissen, wie Sie diese Umgebung verwenden können, können Sie diese Projekte einfach herunterladen und starten. Gehen Sie dazu so vor:

  • Klicken Sie auf die Schaltfläche zum Herunterladen.
  • Inhalt der TAR-Datei in ein Verzeichnis extrahieren
  • Installieren Sie einen dev-Server, der Bare-Modul-Bezeichner auflösen kann. Das Lit-Team empfiehlt @web/dev-server.
  • Führen Sie den dev-Server aus und öffnen Sie Ihren Browser (wenn Sie @web/dev-server verwenden, können Sie npx web-dev-server --node-resolve --watch --open verwenden)
    • Wenn Sie das Beispiel package.json verwenden, verwenden Sie npm run serve.

3. Benutzerdefiniertes Element definieren

Benutzerdefinierte Elemente

Webkomponenten sind eine Sammlung von vier nativen Web-APIs. Sie sind:

  • ES-Module
  • Benutzerdefinierte Elemente
  • Schatten-DOM
  • HTML-Vorlagen

Du hast bereits die ES-Modulspezifikation verwendet, mit der du JavaScript-Module mit Importen und Exporten erstellen kannst, die mit <script type="module"> in die Seite geladen werden.

Ein benutzerdefiniertes Element definieren

Mit der Spezifikation für benutzerdefinierte Elemente können Nutzer ihre eigenen HTML-Elemente mithilfe von JavaScript definieren. Die Namen müssen einen Bindestrich (-) enthalten, damit sie von nativen Browserelementen unterschieden werden können. Löschen Sie die Datei index.js und definieren Sie eine benutzerdefinierte Elementklasse:

index.js

class RatingElement extends HTMLElement {}

customElements.define('rating-element', RatingElement);

Ein benutzerdefiniertes Element wird definiert, indem eine Klasse, die HTMLElement erweitert, mit einem Tag-Namen mit Bindestrich verknüpft wird. Der Aufruf von customElements.define weist den Browser an, die Klasse RatingElement mit dem tagName ‘rating-element' zu verknüpfen. Das bedeutet, dass jedes Element in Ihrem Dokument mit dem Namen <rating-element> mit dieser Klasse verknüpft wird.

Füge ein <rating-element>-Objekt in den Dokumenttext ein und sieh dir an, was gerendert wird.

index.html

<body>
 <rating-element></rating-element>
</body>

In der Ausgabe sehen Sie, dass nichts gerendert wurde. Das ist ganz normal, weil du dem Browser nicht mitgeteilt hast, wie <rating-element> gerendert werden soll. Sie können prüfen, ob die Definition des benutzerdefinierten Elements erfolgreich war, indem Sie in den Chrome-Entwicklertools das <rating-element> auswählen. Element-Selektor und in der Konsole den Aufruf:

$0.constructor

Es sollte Folgendes ausgegeben werden:

class RatingElement extends HTMLElement {}

Lebenszyklus benutzerdefinierter Elemente

Benutzerdefinierte Elemente enthalten eine Reihe von Lebenszyklus-Hooks. Sie sind:

  • constructor
  • connectedCallback
  • disconnectedCallback
  • attributeChangedCallback
  • adoptedCallback

constructor wird beim erstmaligen Erstellen des Elements aufgerufen, z. B. durch Aufrufen von document.createElement(‘rating-element') oder new RatingElement(). Der Konstruktor eignet sich gut zum Einrichten Ihres Elements, es wird jedoch in der Regel als nicht praktikabel angesehen, DOM-Änderungen im Konstruktor für das Element "Boot-up" durchzuführen. aus Gründen der Leistung.

connectedCallback wird aufgerufen, wenn das benutzerdefinierte Element an das DOM angehängt wird. Hier finden in der Regel die ersten DOM-Änderungen statt.

disconnectedCallback wird aufgerufen, nachdem das benutzerdefinierte Element aus dem DOM entfernt wurde.

attributeChangedCallback(attrName, oldValue, newValue) wird aufgerufen, wenn sich eines der benutzerdefinierten Attribute ändert.

Das adoptedCallback wird aufgerufen, wenn das benutzerdefinierte Element von einem anderen documentFragment über adoptNode in das Hauptdokument übernommen wird, z. B. in HTMLTemplateElement.

DOM rendern

Kehren Sie nun zum benutzerdefinierten Element zurück und verknüpfen Sie es mit einem DOM. Legen Sie den Inhalt des Elements fest, wenn es an das DOM angehängt wird:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   this.innerHTML = `
     <style>
       rating-element {
         display: inline-flex;
         align-items: center;
       }
       rating-element button {
         background: transparent;
         border: none;
         cursor: pointer;
       }
     </style>
     <button class="thumb_down" >
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
     </button>
     <span class="rating">${this.rating}</span>
     <button class="thumb_up">
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
     </button>
   `;
 }
}

customElements.define('rating-element', RatingElement);

In der constructor speichern Sie eine Instanzeigenschaft namens rating für das Element. In der connectedCallback fügen Sie <rating-element> untergeordnete DOM-Elemente hinzu, um die aktuelle Bewertung sowie die Schaltflächen „Mag ich“ und „Mag ich nicht“ anzuzeigen.

4. Schatten-DOM

Warum Shadow DOM?

Im vorherigen Schritt werden Sie feststellen, dass die Selektoren im Stil-Tag, das Sie eingefügt haben, ein beliebiges Bewertungselement auf der Seite sowie alle Schaltflächen auswählen. Dies kann dazu führen, dass die Stile aus dem Element herausragen und andere Knoten auswählen, die Sie möglicherweise nicht gestalten möchten. Darüber hinaus können die Knoten innerhalb Ihres benutzerdefinierten Elements durch andere Stile außerhalb dieses benutzerdefinierten Elements ungewollt formatiert werden. Versuchen Sie beispielsweise, ein Stil-Tag in den Header des Hauptdokuments einzufügen:

index.html

<!DOCTYPE html>
<html>
 <head>
   <script src="./index.js" type="module"></script>
   <style>
     span {
       border: 1px solid red;
     }
   </style>
 </head>
 <body>
   <rating-element></rating-element>
 </body>
</html>

Die Ausgabe sollte einen roten Rahmen um die Spanne für die Bewertung haben. Dies ist ein trivialer Fall, aber die fehlende DOM-Kapselung kann bei komplexeren Anwendungen zu größeren Problemen führen. Hier kommt Shadow DOM ins Spiel.

Schattenstamm hinzufügen

Hängen Sie einen Schattenstamm an das Element an und rendern Sie das DOM innerhalb dieses Stamms:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   const shadowRoot = this.attachShadow({mode: 'open'});

   shadowRoot.innerHTML = `
     <style>
       :host {
         display: inline-flex;
         align-items: center;
       }
       button {
         background: transparent;
         border: none;
         cursor: pointer;
       }
     </style>
     <button class="thumb_down" >
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
     </button>
     <span class="rating">${this.rating}</span>
     <button class="thumb_up">
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
     </button>
   `;
 }
}

customElements.define('rating-element', RatingElement);

Wenn Sie die Seite aktualisieren, werden Sie feststellen, dass die Knoten im Shadow Root nicht mehr mit den Stilen im Hauptdokument ausgewählt werden können.

Wie gehen Sie dazu vor? In der connectedCallback haben Sie this.attachShadow aufgerufen, wodurch ein Schattenstamm an ein Element angehängt wird. Der Modus open bedeutet, dass der Schatteninhalt inspiziert werden kann und auch der Schattenstamm über this.shadowRoot zugänglich ist. Sehen Sie sich auch die Webkomponente im Chrome Inspector an:

Der Dom-Baum im Chrome Inspector. Es gibt ein <rating-element> mit a#shadow-root (open) als untergeordnetem Element und dem DOM von vorher innerhalb dieses shadowroot.

Sie sollten nun einen maximierbaren Schattenstamm sehen, der den Inhalt enthält. Alles in dieser Schattenwurzel wird als Shadow DOM bezeichnet. Wenn Sie das Bewertungselement in den Chrome-Entwicklertools auswählen und $0.children aufrufen, werden Sie feststellen, dass es keine untergeordneten Elemente zurückgibt. Das liegt daran, dass das Shadow DOM nicht als Teil desselben DOM-Baums wie direkte untergeordnete Elemente angesehen wird, sondern als Schattenbaum.

Light DOM

Experiment: Fügen Sie einen Knoten als direktes untergeordnetes Element von <rating-element> hinzu:

index.html

<rating-element>
 <div>
   This is the light DOM!
 </div>
</rating-element>

Wenn Sie die Seite aktualisieren, werden Sie feststellen, dass dieser neue DOM-Knoten im Light DOM dieses benutzerdefinierten Elements nicht auf der Seite angezeigt wird. Das liegt daran, dass Shadow DOM Funktionen hat, mit denen gesteuert wird, wie Light DOM-Knoten über <slot>-Elemente in den Schattenbereich projiziert werden.

5. HTML-Vorlagen

Warum Vorlagen?

Die Verwendung von innerHTML und Vorlagenliteral-Strings ohne Bereinigung kann zu Sicherheitsproblemen bei der Skripteinschleusung führen. In der Vergangenheit war die Verwendung von DocumentFragments z. B. mit anderen Problemen verbunden, z. B. beim Laden von Bildern und beim Ausführen von Skripts, wenn die Vorlagen definiert wurden. Auch die Wiederverwendbarkeit wird behindert. Hier kommt das Element <template> ins Spiel: Vorlagen bieten ein inaktives DOM, eine leistungsstarke Methode zum Klonen von Knoten, sowie wiederverwendbare Vorlagen.

Vorlagen verwenden

Stellen Sie als Nächstes die Komponente auf die Verwendung von HTML-Vorlagen um:

index.html

<body>
 <template id="rating-element-template">
   <style>
     :host {
       display: inline-flex;
       align-items: center;
     }
     button {
       background: transparent;
       border: none;
       cursor: pointer;
     }
   </style>
   <button class="thumb_down" >
     <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
   </button>
   <span class="rating"></span>
   <button class="thumb_up">
     <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
   </button>
 </template>

 <rating-element>
   <div>
     This is the light DOM!
   </div>
 </rating-element>
</body>

Hier haben Sie den DOM-Inhalt in ein Vorlagen-Tag im DOM des Hauptdokuments verschoben. Refaktorieren Sie nun die Definition des benutzerdefinierten Elements:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   const shadowRoot = this.attachShadow({mode: 'open'});
   const templateContent = document.getElementById('rating-element-template').content;
   const clonedContent = templateContent.cloneNode(true);
   shadowRoot.appendChild(clonedContent);

   this.shadowRoot.querySelector('.rating').innerText = this.rating;
 }
}

customElements.define('rating-element', RatingElement);

Wenn Sie dieses Vorlagenelement verwenden möchten, fragen Sie die Vorlage ab, rufen ihren Inhalt ab und klonen diese Knoten mit templateContent.cloneNode, wobei das Argument true einen Deep-Klon ausführt. Anschließend initialisieren Sie den Dom mit den Daten.

Herzlichen Glückwunsch, Sie haben jetzt eine Webkomponente! Leider ist noch nichts, also fügen Sie als Nächstes einige Funktionen hinzu.

6. Funktion hinzufügen

Property-Bindungen

Derzeit kann die Bewertung für das Bewertungselement nur festgelegt werden, indem das Element konstruiert, die Eigenschaft rating für das Objekt festgelegt und es dann auf der Seite eingefügt wird. Leider funktionieren native HTML-Elemente normalerweise nicht so. Native HTML-Elemente werden tendenziell bei Änderungen an Eigenschaften und Attributen aktualisiert.

Damit die Ansicht durch das benutzerdefinierte Element aktualisiert wird, wenn sich die Eigenschaft rating ändert, fügen Sie die folgenden Zeilen hinzu:

index.js

constructor() {
  super();
  this._rating = 0;
}

set rating(value) {
  this._rating = value;
  if (!this.shadowRoot) {
    return;
  }

  const ratingEl = this.shadowRoot.querySelector('.rating');
  if (ratingEl) {
    ratingEl.innerText = this._rating;
  }
}

get rating() {
  return this._rating;
}

Sie fügen einen Setter und Getter für die Eigenschaft „rating“ hinzu und aktualisieren dann den Text des Bewertungselements, sofern verfügbar. Wenn Sie also die Eigenschaft rating für das Element festlegen, wird die Ansicht aktualisiert. können Sie es kurz in der Entwicklertools-Konsole testen.

Attributbindungen

Aktualisieren Sie nun die Ansicht, wenn sich das Attribut ändert. Dies ähnelt einer Eingabe, die ihre Ansicht aktualisiert, wenn Sie <input value="newValue"> festlegen. Glücklicherweise umfasst der Lebenszyklus der Webkomponente die attributeChangedCallback. Aktualisieren Sie die Bewertung, indem Sie die folgenden Zeilen hinzufügen:

index.js

static get observedAttributes() {
 return ['rating'];
}

attributeChangedCallback(attributeName, oldValue, newValue) {
 if (attributeName === 'rating') {
   const newRating = Number(newValue);
   this.rating = newRating;
 }
}

Damit attributeChangedCallback ausgelöst wird, musst du einen statischen Getter für RatingElement.observedAttributes which defines the attributes to be observed for changes festlegen. Anschließend legen Sie die Bewertung deklarativ im DOM fest. Probieren Sie es aus:

index.html

<rating-element rating="5"></rating-element>

Die Bewertung sollte jetzt deklarativ aktualisiert werden.

Schaltflächenfunktionen

Jetzt fehlen nur noch die Schaltflächen. Das Verhalten dieser Komponente sollte es dem Nutzer ermöglichen, eine einzelne positive oder negative Bewertung abzugeben und dem Nutzer visuelles Feedback zu geben. Sie können dies mit einigen Ereignis-Listenern und einer spiegelnden -Eigenschaft implementieren, aber zuerst aktualisieren Sie die Stile, um visuelles Feedback zu geben, indem Sie die folgenden Zeilen anhängen:

index.html

<style>
...

 :host([vote=up]) .thumb_up {
   fill: green;
 }
  :host([vote=down]) .thumb_down {
   fill: red;
 }
</style>

Im Shadow DOM bezieht sich der :host-Selektor auf den Knoten oder das benutzerdefinierte Element, mit dem der Schattenstamm verknüpft ist. Wenn das Attribut vote den Wert "up" hat, ist die Schaltfläche mit dem Daumen nach oben grün, aber wenn vote auf "down", then it will turn the thumb-down button red gesetzt ist. Implementieren Sie nun die entsprechende Logik, indem Sie eine reflektierende Eigenschaft bzw. ein reflektierendes Attribut für vote erstellen, ähnlich wie bei der Implementierung von rating. Beginnen Sie mit dem Property-Setter und -Getter:

index.js

constructor() {
  super();
  this._rating = 0;
  this._vote = null;
}

set vote(newValue) {
  const oldValue = this._vote;
  if (newValue === oldValue) {
    return;
  }
  if (newValue === 'up') {
    if (oldValue === 'down') {
      this.rating += 2;
    } else {
      this.rating += 1;
    }
  } else if (newValue === 'down') {
    if (oldValue === 'up') {
      this.rating -= 2;
    } else {
      this.rating -= 1;
    }
  }
  this._vote = newValue;
  this.setAttribute('vote', newValue);
}

get vote() {
  return this._vote;
}

Sie initialisieren das Instanzattribut _vote mit null im constructor und prüfen im Setter, ob der neue Wert anders ist. In diesem Fall passen Sie die Bewertung entsprechend an und geben vor allem das Attribut vote mit this.setAttribute an den Host zurück.

Als Nächstes richten Sie die Attributbindung ein:

index.js

static get observedAttributes() {
  return ['rating', 'vote'];
}

attributeChangedCallback(attributeName, oldValue, newValue) {
  if (attributeName === 'rating') {
    const newRating = Number(newValue);

    this.rating = newRating;
  } else if (attributeName === 'vote') {
    this.vote = newValue;
  }
}

Dies ist derselbe Vorgang, den Sie bei der rating-Attributbindung ausgeführt haben: fügen Sie vote dem observedAttributes hinzu und legen die Eigenschaft vote in der attributeChangedCallback fest. Fügen Sie nun einige Click-Event-Listener hinzu, um den Schaltflächen Funktionen zu bieten.

index.js

constructor() {
 super();
 this._rating = 0;
 this._vote = null;
 this._boundOnUpClick = this._onUpClick.bind(this);
 this._boundOnDownClick = this._onDownClick.bind(this);
}

connectedCallback() {
  ...
  this.shadowRoot.querySelector('.thumb_up')
    .addEventListener('click', this._boundOnUpClick);
  this.shadowRoot.querySelector('.thumb_down')
    .addEventListener('click', this._boundOnDownClick);
}

disconnectedCallback() {
  this.shadowRoot.querySelector('.thumb_up')
    .removeEventListener('click', this._boundOnUpClick);
  this.shadowRoot.querySelector('.thumb_down')
    .removeEventListener('click', this._boundOnDownClick);
}

_onUpClick() {
  this.vote = 'up';
}

_onDownClick() {
  this.vote = 'down';
}

In der constructor binden Sie einige Klick-Listener an das Element und behalten die Verweise bei. In der connectedCallback werden Click-Events auf die Schaltflächen überwacht. In der disconnectedCallback bereinigen Sie diese Listener und auf den Klick-Listenern selbst legen Sie vote entsprechend fest.

Herzlichen Glückwunsch! Sie verfügen nun über eine Webkomponente mit allen Funktionen. klicken Sie auf ein paar Schaltflächen! Das Problem ist, dass meine JS-Datei jetzt 96 Zeilen erreicht, meine HTML-Datei 43 Zeilen und der Code ist ziemlich ausführlich und für eine so einfache Komponente unerlässlich. Hier kommt das Lit-Projekt von Google ins Spiel!

7. Lit-HTML

Code-Checkpoint

Warum Lit-HTML?

In erster Linie ist das <template>-Tag nützlich und leistungsfähig, aber es ist nicht mit der Logik der Komponente verpackt, sodass es schwierig ist, die Vorlage mit dem Rest der Logik zu verteilen. Außerdem verleiht die Art und Weise, wie Vorlagenelemente verwendet werden, imperativen Code, was im Vergleich zu deklarativen Codierungsmustern in vielen Fällen zu weniger lesbarem Code führt.

Hier kommt lit-html ins Spiel! Lit HTML ist das Rendering-System von Lit, mit dem Sie HTML-Vorlagen in JavaScript schreiben und diese Vorlagen dann gemeinsam mit Daten effizient rendern und neu rendern können, um DOM zu erstellen und zu aktualisieren. Es ähnelt den beliebten JSX- und VDOM-Bibliotheken, wird jedoch nativ im Browser ausgeführt und ist in vielen Fällen viel effizienter.

Lit HTML verwenden

Migrieren Sie als Nächstes die native Webkomponente rating-element zur Verwendung von Lit-Vorlagen, die getaggte Vorlagenliterale verwenden. Dabei handelt es sich um Funktionen, die Vorlagenstrings als Argumente mit einer speziellen Syntax annehmen. Die Lit nutzt dann Vorlagenelemente im Hintergrund, um ein schnelles Rendering zu ermöglichen und einige Sicherheitsfunktionen zur Verfügung zu stellen. Migrieren Sie zuerst <template> aus index.html in eine Lit-Vorlage. Fügen Sie dazu der WebComponent eine render()-Methode hinzu:

index.js

// Dont forget to import from Lit!
import {render, html} from 'lit';

class RatingElement extends HTMLElement {
  ...
  render() {
    if (!this.shadowRoot) {
      return;
    }

    const template = html`
      <style>
        :host {
          display: inline-flex;
          align-items: center;
        }
        button {
          background: transparent;
          border: none;
          cursor: pointer;
        }

       :host([vote=up]) .thumb_up {
         fill: green;
       }

       :host([vote=down]) .thumb_down {
         fill: red;
       }
      </style>
      <button class="thumb_down">
        <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
      </button>
      <span class="rating">${this.rating}</span>
      <button class="thumb_up">
        <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
      </button>`;

    render(template, this.shadowRoot);
  }
}

Sie können Ihre Vorlage auch aus index.html löschen. Bei dieser Renderingmethode definieren Sie eine Variable namens template und rufen die mit html getaggte Vorlagenliteralfunktion auf. Außerdem werden Sie feststellen, dass Sie eine einfache Datenbindung innerhalb des span.rating-Elements durchgeführt haben, indem Sie die Vorlagen-Literal-Interpolationssyntax ${...} verwendet haben. Dies bedeutet, dass Sie diesen Knoten später nicht mehr zwingend aktualisieren müssen. Außerdem rufen Sie die beleuchtete render-Methode auf, mit der die Vorlage synchron im Schattenstamm gerendert wird.

Zur deklarativen Syntax migrieren

Nachdem Sie das <template>-Element entfernt haben, refaktorieren Sie den Code so, dass stattdessen die neu definierte Methode render aufgerufen wird. Sie können zunächst die Event-Listener-Bindung von Licht nutzen, um den Listener-Code zu bereinigen:

index.js

<button
    class="thumb_down"
    @click=${() => {this.vote = 'down'}}>
...
<button
    class="thumb_up"
    @click=${() => {this.vote = 'up'}}>

Mit Lit-Vorlagen kann einem Knoten mit der Bindungssyntax @EVENT_NAME ein Ereignis-Listener hinzugefügt werden. In diesem Fall wird das Attribut vote jedes Mal aktualisiert, wenn auf diese Schaltflächen geklickt wird.

Bereinigen Sie als Nächstes den Initialisierungscode des Event-Listeners in constructor, connectedCallback und disconnectedCallback:

index.js

constructor() {
  super();
  this._rating = 0;
  this._vote = null;
}

connectedCallback() {
  this.attachShadow({mode: 'open'});
  this.render();
}

// remove disonnectedCallback and _onUpClick and _onDownClick

Sie konnten die Klick-Listener-Logik aus allen drei Callbacks entfernen und sogar disconnectedCallback vollständig entfernen. Außerdem konnten Sie den gesamten DOM-Initialisierungscode aus der Datei connectedCallback entfernen, um sie deutlich eleganter zu gestalten. Das bedeutet auch, dass Sie die Listener-Methoden _onUpClick und _onDownClick entfernen können.

Aktualisieren Sie schließlich die Attribut-Setter, um die render-Methode zu verwenden, damit der Dom aktualisiert werden kann, wenn sich die Attribute oder Attribute ändern:

index.js

set rating(value) {
  this._rating = value;
  this.render();
}

...

set vote(newValue) {
  const oldValue = this._vote;
  if (newValue === oldValue) {
    return;
  }

  if (newValue === 'up') {
    if (oldValue === 'down') {
      this.rating += 2;
    } else {
      this.rating += 1;
    }
  } else if (newValue === 'down') {
    if (oldValue === 'up') {
      this.rating -= 2;
    } else {
      this.rating -= 1;
    }
  }

  this._vote = newValue;
  this.setAttribute('vote', newValue);
  // add render method
  this.render();
}

Hier konnten Sie die Logik für die Aktualisierung der Domain aus dem rating-Setter entfernen und einen Aufruf von render aus dem vote-Setter hinzugefügt. Die Vorlage ist jetzt viel besser lesbar, da Sie sehen können, wo die Bindungen und Event-Listener angewendet werden.

Nachdem Sie die Seite aktualisiert haben, sollte eine funktionierende Bewertungsschaltfläche angezeigt werden, die so aussehen sollte, wenn Sie auf die positive Bewertung klicken.

Schieberegler für die Bewertung „Mag ich“ und „Mag ich nicht“ mit einem Wert von 6 und dem Schieberegler für die Bewertung „Mag ich“ in Grün

8. LitElement

Warum LitElement

Es liegen immer noch einige Probleme mit dem Code vor. Erstens: Wenn du die Eigenschaft oder das Attribut vote änderst, kann dies die Eigenschaft rating ändern, was dazu führt, dass render zweimal aufgerufen wird. Obwohl wiederholte Rendering-Aufrufe im Grunde betriebsunabhängig und effizient sind, verbringt die JavaScript-VM immer noch Zeit, diese Funktion zweimal synchron aufzurufen. Zweitens ist es mühsam, neue Eigenschaften und Attribute hinzuzufügen, da es sehr viel Boilerplate-Code erfordert. Hier kommt LitElement ins Spiel.

LitElement ist die Basisklasse von Lit zum Erstellen schneller, einfacher Webkomponenten, die in Frameworks und Umgebungen verwendet werden können. Sehen Sie sich als Nächstes an, was LitElement für uns in rating-element tun kann, indem Sie die Implementierung ändern, um sie zu verwenden.

LitElement verwenden

Importieren Sie zuerst die LitElement-Basisklasse und erstellen Sie abgeleitete Klassen aus dem lit-Paket:

index.js

import {LitElement, html, css} from 'lit';

class RatingElement extends LitElement {
// remove connectedCallback()
...

Sie importieren LitElement. Dies ist die neue Basisklasse für die rating-element. Als Nächstes behalten Sie den Import „html“ und schließlich „css“ bei. So können wir Vorlagenliterale mit CSS-Tags für CSS-Mathematik, Vorlagen und andere Funktionen definieren.

Verschieben Sie als Nächstes die Stile von der Rendering-Methode in das statische Stylesheet von Lit:

index.js

class RatingElement extends LitElement {
  static get styles() {
    return css`
      :host {
        display: inline-flex;
        align-items: center;
      }
      button {
        background: transparent;
        border: none;
        cursor: pointer;
      }

      :host([vote=up]) .thumb_up {
        fill: green;
      }

      :host([vote=down]) .thumb_down {
        fill: red;
      }
    `;
  }
 ...

Hier befinden sich die meisten Stile in Lit. Die Bibliotheken werden auf diese Stile zurückgreifen und Browserfunktionen wie konstruierbare Stylesheets verwenden, um schnellere Renderingzeiten zu ermöglichen. Außerdem werden die Daten bei Bedarf über das Polyfill in älteren Browsern durch das Webkomponenten-Polyfill bereitgestellt.

Lebenszyklus

In Lit wird zusätzlich zu den nativen Webkomponenten-Callbacks eine Reihe von Callback-Methoden für den Renderinglebenszyklus eingeführt. Diese Callbacks werden ausgelöst, wenn deklarierte Lit-Eigenschaften geändert werden.

Damit Sie diese Funktion verwenden können, müssen Sie statisch deklarieren, welche Attribute den Rendering-Lebenszyklus auslösen.

index.js

static get properties() {
  return {
    rating: {
      type: Number,
    },
    vote: {
      type: String,
      reflect: true,
    }
  };
}

// remove observedAttributes() and attributeChangedCallback()
// remove set rating() get rating()

Hier definieren Sie, dass rating und vote den LitElement-Rendering-Lebenszyklus auslösen und die Typen definieren, die zum Konvertieren der Stringattribute in Attribute verwendet werden.

<user-profile .name=${this.user.name} .age=${this.user.age}>
  ${this.user.family.map(member => html`
        <family-member
             .name=${member.name}
             .relation=${member.relation}>
        </family-member>`)}
</user-profile>

Außerdem aktualisiert das Flag reflect für die Property vote automatisch das vote-Attribut des Hostelements, das du manuell im vote-Setter ausgelöst hast.

Da Sie nun über den Block für statische Eigenschaften verfügen, können Sie die Logik für die Aktualisierung von Attributen und Eigenschaften entfernen. Das bedeutet, dass Sie die folgenden Methoden entfernen können:

  • connectedCallback
  • observedAttributes
  • attributeChangedCallback
  • rating (Setter und Getter)
  • vote (Setter und Getter, aber die Änderungslogik des Setters beibehalten)

Sie behalten jedoch die constructor und fügen eine neue willUpdate-Lebenszyklusmethode hinzu:

index.js

constructor() {
  super();
  this.rating = 0;
  this.vote = null;
}

willUpdate(changedProps) {
  if (changedProps.has('vote')) {
    const newValue = this.vote;
    const oldValue = changedProps.get('vote');

    if (newValue === 'up') {
      if (oldValue === 'down') {
        this.rating += 2;
      } else {
        this.rating += 1;
      }
    } else if (newValue === 'down') {
      if (oldValue === 'up') {
        this.rating -= 2;
      } else {
        this.rating -= 1;
      }
    }
  }
}

// remove set vote() and get vote()

Hier initialisieren Sie einfach rating und vote und verschieben die Setter-Logik vote in die Lebenszyklusmethode willUpdate. Die Methode willUpdate wird immer vor render aufgerufen, wenn eine aktualisierende Eigenschaft geändert wird, da LitElement mehrere Attributänderungen in Batches fasst und das Rendering asynchron ausführt. Änderungen an reaktiven Attributen wie this.rating in willUpdate lösen keine unnötigen render-Lebenszyklusaufrufe aus.

Schließlich ist render eine LitElement-Lebenszyklusmethode, bei der eine Lit-Vorlage zurückgegeben werden muss:

index.js

render() {
  return html`
    <button
        class="thumb_down"
        @click=${() => {this.vote = 'down'}}>
      <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
    </button>
    <span class="rating">${this.rating}</span>
    <button
        class="thumb_up"
        @click=${() => {this.vote = 'up'}}>
      <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
    </button>`;
}

Sie müssen nicht mehr nach dem Schattenstamm suchen und die Funktion render, die zuvor aus dem Paket 'lit' importiert wurde, nicht mehr aufrufen.

Ihr Element sollte jetzt in der Vorschau gerendert werden. ein Klick!

9. Glückwunsch

Herzlichen Glückwunsch, Sie haben eine Webkomponente von Grund auf erfolgreich erstellt und zu einem LitElement entwickelt!

Es ist superklein (< 5 KB reduziert und gzip), superschnell und macht das Programmieren wirklich Spaß! Sie können Komponenten für andere Frameworks erstellen oder vollwertige Anwendungen damit erstellen.

Sie wissen jetzt, was Webkomponenten sind, wie sie erstellt werden und wie sie mit Lit einfacher erstellt werden können.

Code-Checkpoint

Möchten Sie Ihren endgültigen Code mit unserem vergleichen? Hier können Sie sie vergleichen.

Was liegt als Nächstes an?

Sehen Sie sich auch die anderen Codelabs an.

Weitere Informationen

Community