1. Einführung
Was ist Lit?
Lit ist eine einfache Bibliothek zum Erstellen schneller, schlanker Webkomponenten, die in jedem Framework oder ohne Framework funktionieren. Mit Lit können Sie wiederverwendbare Komponenten, Anwendungen, Designsysteme und mehr erstellen.
Lerninhalte
So übersetzen Sie mehrere React-Konzepte in Lit, z. B.:
- JSX und Vorlagen
- Komponenten und Attribute
- Zustand und Lebenszyklus
- Aufhänger
- Kinder
- Refs
- Vermittlungsstatus
Aufgaben
Am Ende dieses Codelabs können Sie Konzepte von React-Komponenten in ihre Lit-Analoga umwandeln.
Voraussetzungen
- Die aktuelle Version von Chrome, Safari, Firefox oder Edge.
- Kenntnisse von HTML, CSS, JavaScript und den Chrome-Entwicklertools
- React-Kenntnisse
- (Erweitert) Wenn Sie die beste Entwicklungsumgebung nutzen möchten, laden Sie VS Code herunter. Sie benötigen außerdem lit-plugin für VS Code und NPM.
2. Lit im Vergleich zu React
Die Kernkonzepte und ‑funktionen von Lit ähneln in vielerlei Hinsicht denen von React, es gibt aber auch einige wichtige Unterschiede und Unterscheidungsmerkmale:
Es ist klein
Lit ist sehr klein: Die komprimierte und gezippte Version ist nur etwa 5 KB groß, während React + ReactDOM über 40 KB groß sind.

Es ist schnell
In öffentlichen Benchmarks, in denen das Templating-System von Lit, lit-html, mit dem VDOM von React verglichen wird, ist lit-html im schlimmsten Fall 8–10% schneller als React und in den häufigeren Anwendungsfällen über 50%schneller.
LitElement (die Komponentenbasisklasse von Lit) verursacht nur einen minimalen Overhead für lit-html, übertrifft die Leistung von React jedoch um 16–30%, wenn Komponentenfunktionen wie Arbeitsspeichernutzung, Interaktions- und Startzeiten verglichen werden.

Kein Build erforderlich
Dank neuer Browserfunktionen wie ES-Modulen und getaggten Template-Literalen muss Lit nicht kompiliert werden, um ausgeführt zu werden. Das bedeutet, dass Entwicklungsumgebungen mit einem Script-Tag, einem Browser und einem Server eingerichtet werden können.
Mit ES-Modulen und modernen CDNs wie Skypack oder UNPKG benötigen Sie möglicherweise nicht einmal NPM, um loszulegen.
Wenn Sie möchten, können Sie aber weiterhin Lit-Code erstellen und optimieren. Die jüngste Konsolidierung von Entwicklern rund um native ES-Module war gut für Lit. Lit ist nur normales JavaScript und es sind keine frameworkspezifischen CLIs oder Build-Verarbeitung erforderlich.
Unabhängig vom Framework
Die Komponenten von Lit basieren auf einer Reihe von Webstandards, die als Webkomponenten bezeichnet werden. Das bedeutet, dass eine in Lit erstellte Komponente in aktuellen und zukünftigen Frameworks funktioniert. Wenn es HTML-Elemente unterstützt, unterstützt es auch Webkomponenten.
Probleme mit der Framework-Interoperabilität treten nur auf, wenn die Frameworks eine restriktive Unterstützung für das DOM haben. React ist eines dieser Frameworks, bietet aber über Refs die Möglichkeit, sich aus dem Framework zu befreien. Refs in React sind jedoch nicht benutzerfreundlich.
Das Lit-Team arbeitet an einem experimentellen Projekt namens @lit-labs/react, das Ihre Lit-Komponenten automatisch parst und einen React-Wrapper generiert, sodass Sie keine Referenzen verwenden müssen.
Außerdem erfahren Sie unter Custom Elements Everywhere, welche Frameworks und Bibliotheken gut mit benutzerdefinierten Elementen funktionieren.
Erstklassige TypeScript-Unterstützung
Es ist zwar möglich, den gesamten Lit-Code in JavaScript zu schreiben, aber Lit ist in TypeScript geschrieben und das Lit-Team empfiehlt Entwicklern, ebenfalls TypeScript zu verwenden.
Das Lit-Team hat mit der Lit-Community zusammengearbeitet, um Projekte zu pflegen, die TypeScript-Typprüfung und Intellisense für Lit-Vorlagen sowohl zur Entwicklungs- als auch zur Build-Zeit mit lit-analyzer und lit-plugin ermöglichen.


Entwicklertools sind in den Browser integriert
Lit-Komponenten sind nur HTML-Elemente im DOM. Das bedeutet, dass Sie keine Tools oder Erweiterungen für Ihren Browser installieren müssen, um Ihre Komponenten zu prüfen.
Sie können einfach die Entwicklertools öffnen, ein Element auswählen und seine Eigenschaften oder seinen Status untersuchen.

Es wurde mit Blick auf das serverseitige Rendering (SSR) entwickelt.
Lit 2 wurde mit Blick auf die Unterstützung von SSR entwickelt. Zum Zeitpunkt der Erstellung dieses Codelabs hat das Lit-Team die SSR-Tools noch nicht in einer stabilen Form veröffentlicht. Das Lit-Team hat jedoch bereits serverseitig gerenderte Komponenten in Google-Produkten bereitgestellt und SSR in React-Anwendungen getestet. Das Lit-Team geht davon aus, dass diese Tools bald extern auf GitHub veröffentlicht werden.
In der Zwischenzeit können Sie den Fortschritt des Lit-Teams hier verfolgen.
Geringe Akzeptanz
Für die Nutzung von Lit ist kein großer Aufwand erforderlich. Sie können Komponenten in Lit erstellen und sie Ihrem bestehenden Projekt hinzufügen. Wenn Sie sie nicht mögen, müssen Sie nicht die gesamte App auf einmal konvertieren, da Webkomponenten auch in anderen Frameworks funktionieren.
Haben Sie eine ganze App in Lit erstellt und möchten zu etwas anderem wechseln? Dann können Sie Ihre aktuelle Lit-Anwendung in Ihr neues Framework einfügen und alles, was Sie möchten, zu den Komponenten des neuen Frameworks migrieren.
Außerdem unterstützen viele moderne Frameworks die Ausgabe in Webkomponenten. Das bedeutet, dass sie in der Regel selbst in ein Lit-Element passen.
3. Einrichtung und Nutzung des Playgrounds
Für dieses Codelab gibt es zwei Möglichkeiten:
- Sie können alles online im Browser erledigen.
- (Erweitert) Sie können dies auf Ihrem lokalen Computer mit VS Code tun.
Auf den Code zugreifen
Im gesamten Codelab finden Sie Links zum Lit-Playground, z. B.:
Die Playground-Umgebung ist eine Code-Sandbox, die vollständig in Ihrem Browser ausgeführt wird. Es kann TypeScript- und JavaScript-Dateien kompilieren und ausführen und auch automatisch Importe in Knotenmodule auflösen, z. B.
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://cdn.skypack.dev/lit';
Sie können das gesamte Tutorial im Lit-Playground durcharbeiten und diese Prüfpunkte als Ausgangspunkt verwenden. Wenn Sie VS Code verwenden, können Sie diese Prüfpunkte nutzen, um den Startcode für einen beliebigen Schritt herunterzuladen und Ihre Arbeit zu überprüfen.
Benutzeroberfläche des beleuchteten Spielplatzes

Der Screenshot der Lit-Playground-Benutzeroberfläche zeigt die Abschnitte, die Sie in diesem Codelab verwenden werden.
- Dateiauswahl Beachten Sie das Pluszeichen…
- Dateieditor
- Codevorschau
- Schaltfläche „Aktualisieren“
- Grafik: Symbol zum Herunterladen
VS Code-Einrichtung (Advanced)
Diese VS Code-Einrichtung bietet folgende Vorteile:
- Vorlagentyp prüfen
- IntelliSense und automatische Vervollständigung für Vorlagen
Wenn Sie NPM und VS Code (mit dem lit-plugin-Plug-in) bereits installiert haben und wissen, wie Sie diese Umgebung verwenden, können Sie diese Projekte einfach herunterladen und starten. Gehen Sie dazu so vor:
- Drücken Sie die Schaltfläche „Herunterladen“.
- Inhalt der TAR-Datei in ein Verzeichnis extrahieren
- (Falls TS) Richten Sie eine schnelle tsconfig ein, die ES-Module und ES2015+ ausgibt.
- Installieren Sie einen Entwicklungsserver, der Bare-Modulspezifizierer auflösen kann. Das Lit-Team empfiehlt @web/dev-server.
- Hier ein Beispiel:
package.json
- Hier ein Beispiel:
- Führen Sie den Entwicklungsserver aus und öffnen Sie Ihren Browser. Wenn Sie @web/dev-server verwenden, können Sie
npx web-dev-server --node-resolve --watch --openverwenden.- Wenn Sie das Beispiel
package.jsonverwenden, nutzen Sienpm run dev.
- Wenn Sie das Beispiel
4. JSX und Vorlagen
In diesem Abschnitt lernen Sie die Grundlagen der Vorlagenerstellung in Lit kennen.
JSX- und Lit-Vorlagen
JSX ist eine Syntaxerweiterung für JavaScript, mit der React-Nutzer ganz einfach Vorlagen in ihren JavaScript-Code schreiben können. Lit-Vorlagen haben einen ähnlichen Zweck: Sie drücken die Benutzeroberfläche einer Komponente als Funktion ihres Status aus.
Grundlegende Syntax
In React rendern Sie ein JSX-„Hello World“ so:
import 'react';
import ReactDOM from 'react-dom';
const name = 'Josh Perez';
const element = (
<>
<h1>Hello, {name}</h1>
<div>How are you?</div>
</>
);
ReactDOM.render(
element,
mountNode
);
Im obigen Beispiel gibt es zwei Elemente und eine eingeschlossene Variable „name“. In Lit gehen Sie so vor:
import {html, render} from 'lit';
const name = 'Josh Perez';
const element = html`
<h1>Hello, ${name}</h1>
<div>How are you?</div>`;
render(
element,
mountNode
);
Beachten Sie, dass für Lit-Vorlagen kein React-Fragment erforderlich ist, um mehrere Elemente in den Vorlagen zu gruppieren.
In Lit werden Vorlagen mit einem html-Tagged Template LiteralLIT umschlossen. Daher kommt auch der Name von Lit.
Vorlagenwerte
In Lit-Vorlagen können andere Lit-Vorlagen verwendet werden, die als TemplateResult bezeichnet werden. Setzen Sie name beispielsweise in Kursiv-Tags (<i>) und in ein getaggtes Vorlagenliteral N.B.. Verwenden Sie dabei das Graviszeichen (`) und nicht das einfache Anführungszeichen (').
import {html, render} from 'lit';
const name = html`<i>Josh Perez</i>`;
const element = html`
<h1>Hello, ${name}</h1>
<div>How are you?</div>`;
render(
element,
mountNode
);
TemplateResults können Arrays, Strings, andere TemplateResults sowie Direktiven akzeptieren.
Versuchen Sie, den folgenden React-Code in Lit zu konvertieren:
const itemsToBuy = [
<li>Bananas</li>,
<li>oranges</li>,
<li>apples</li>,
<li>grapes</li>
];
const element = (
<>
<h1>Things to buy:</h1>
<ol>
{itemsToBuy}
</ol>
</>);
ReactDOM.render(
element,
mountNode
);
Antwort:
import {html, render} from 'lit';
const itemsToBuy = [
html`<li>Bananas</li>`,
html`<li>oranges</li>`,
html`<li>apples</li>`,
html`<li>grapes</li>`
];
const element = html`
<h1>Things to buy:</h1>
<ol>
${itemsToBuy}
</ol>`;
render(
element,
mountNode
);
Props übergeben und festlegen
Einer der größten Unterschiede zwischen der JSX- und der Lit-Syntax ist die Syntax für die Datenbindung. Hier ist ein Beispiel für eine React-Eingabe mit Bindungen:
const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element =
<input
disabled={disabled}
className={`static-class ${myClass}`}
defaultValue={value}/>;
ReactDOM.render(
element,
mountNode
);
Im obigen Beispiel wird eine Eingabe definiert, die Folgendes bewirkt:
- Legt „disabled“ auf eine definierte Variable fest (in diesem Fall „false“).
- Legt die Klasse auf
static-classplus eine Variable (in diesem Fall"static-class my-class") fest. - Standardwert festlegen
In Lit gehen Sie so vor:
import {html, render} from 'lit';
const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element = html`
<input
?disabled=${disabled}
class="static-class ${myClass}"
.value=${value}>`;
render(
element,
mountNode
);
Im Lit-Beispiel wird eine boolesche Bindung hinzugefügt, um das Attribut disabled umzuschalten.
Als Nächstes gibt es eine Bindung direkt an das Attribut class anstelle von className. Dem Attribut class können mehrere Bindungen hinzugefügt werden, es sei denn, Sie verwenden die Direktive classMap, die eine deklarative Hilfsfunktion zum Umschalten von Klassen ist.
Schließlich wird die Eigenschaft value für die Eingabe festgelegt. Im Gegensatz zu React wird das Eingabeelement dadurch nicht auf „Nur lesen“ gesetzt, da es der nativen Implementierung und dem nativen Verhalten von „input“ folgt.
Syntax für die Bindung von Lit-Attributen
html`<my-element ?attribute-name=${booleanVar}>`;
- Das Präfix
?ist die Bindungssyntax zum Ein- und Ausschalten eines Attributs für ein Element. - Entspricht
inputRef.toggleAttribute('attribute-name', booleanVar) - Nützlich für Elemente, die
disabledverwenden, dadisabled="false"vom DOM weiterhin als „true“ gelesen wird, weilinputElement.hasAttribute('disabled') === true
html`<my-element .property-name=${anyVar}>`;
- Das Präfix
.ist die Bindungssyntax zum Festlegen einer Property eines Elements. - Entspricht
inputRef.propertyName = anyVar - Gut für die Übergabe komplexer Daten wie Objekte, Arrays oder Klassen
html`<my-element attribute-name=${stringVar}>`;
- Wird an das Attribut eines Elements gebunden
- Entspricht
inputRef.setAttribute('attribute-name', stringVar) - Gut geeignet für einfache Werte, Selektoren für Stilregeln und querySelectors
Handler übergeben
const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element =
<input
onClick={() => console.log('click')}
onChange={e => console.log(e.target.value)} />;
ReactDOM.render(
element,
mountNode
);
Im obigen Beispiel wird eine Eingabe definiert, die Folgendes bewirkt:
- Das Wort „click“ protokollieren, wenn auf die Eingabe geklickt wird
- Wert der Eingabe protokollieren, wenn der Nutzer ein Zeichen eingibt
In Lit gehen Sie so vor:
import {html, render} from 'lit';
const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element = html`
<input
@click=${() => console.log('click')}
@input=${e => console.log(e.target.value)}>`;
render(
element,
mountNode
);
Im Lit-Beispiel wird dem click-Ereignis mit @click ein Listener hinzugefügt.
Anstelle von onChange wird als Nächstes eine Bindung an das native input-Ereignis von <input> verwendet, da das native change-Ereignis nur für blur ausgelöst wird (React abstrahiert diese Ereignisse).
Syntax für Lit-Event-Handler
html`<my-element @event-name=${() => {...}}></my-element>`;
- Das Präfix
@ist die Bindungssyntax für einen Event-Listener. - Entspricht
inputRef.addEventListener('event-name', ...) - Verwendet native DOM-Ereignisnamen
5. Komponenten und Attribute
In diesem Abschnitt erfahren Sie mehr über die Komponenten und Funktionen der Lit-Klasse. State und Hooks werden in späteren Abschnitten ausführlicher behandelt.
Klassenkomponenten und LitElement
Das Lit-Äquivalent einer React-Klassenkomponente ist LitElement. Das Lit-Konzept der „reaktiven Eigenschaften“ ist eine Kombination aus den Props und dem Status von React. Beispiel:
import React from 'react';
import ReactDOM from 'react-dom';
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = {name: ''};
}
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
const element = <Welcome name="Elliott"/>
ReactDOM.render(
element,
mountNode
);
Im obigen Beispiel gibt es eine React-Komponente, die:
- Rendert einen
name. - Legt den Standardwert von
nameauf einen leeren String ("") fest. - Weist
name"Elliott"neu zu
So würden Sie das in LitElement tun
In TypeScript:
import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
@property({type: String})
name = '';
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
}
In JavaScript:
import {LitElement, html} from 'lit';
class WelcomeBanner extends LitElement {
static get properties() {
return {
name: {type: String}
}
}
constructor() {
super();
this.name = '';
}
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
}
customElements.define('welcome-banner', WelcomeBanner);
Und in der HTML-Datei:
<!-- index.html -->
<head>
<script type="module" src="./index.js"></script>
</head>
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
Hier eine Zusammenfassung der Vorgänge im obigen Beispiel:
@property({type: String})
name = '';
- Definiert eine öffentliche reaktive Eigenschaft – ein Teil der öffentlichen API Ihrer Komponente.
- Stellt (standardmäßig) ein Attribut sowie eine Eigenschaft für Ihre Komponente bereit.
- Definiert, wie das Attribut der Komponente (das ein String ist) in einen Wert übersetzt wird.
static get properties() {
return {
name: {type: String}
}
}
- Dies entspricht dem
@property-TS-Decorator, wird aber nativ in JavaScript ausgeführt.
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
- Diese Funktion wird immer dann aufgerufen, wenn sich eine reaktive Eigenschaft ändert.
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
...
}
- Dadurch wird ein HTML-Element-Tag-Name einer Klassendefinition zugeordnet.
- Gemäß dem Standard für benutzerdefinierte Elemente muss der Tag-Name einen Bindestrich (-) enthalten.
thisin einem LitElement bezieht sich auf die Instanz des benutzerdefinierten Elements (in diesem Fall<welcome-banner>).
customElements.define('welcome-banner', WelcomeBanner);
- Dies ist das JavaScript-Äquivalent des
@customElement-TS-Dekorators.
<head>
<script type="module" src="./index.js"></script>
</head>
- Importiert die Definition des benutzerdefinierten Elements
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
- Fügt das benutzerdefinierte Element der Seite hinzu
- Legt das Attribut
nameauf'Elliott'fest.
Funktionskomponenten
In Lit gibt es keine 1:1-Entsprechung einer Funktionskomponente, da kein JSX oder Präprozessor verwendet wird. Es ist jedoch ganz einfach, eine Funktion zu erstellen, die Eigenschaften entgegennimmt und basierend auf diesen Eigenschaften DOM rendert. Beispiel:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Elliott"/>
ReactDOM.render(
element,
mountNode
);
In Lit wäre das:
import {html, render} from 'lit';
function Welcome(props) {
return html`<h1>Hello, ${props.name}</h1>`;
}
render(
Welcome({name: 'Elliott'}),
document.body.querySelector('#root')
);
6. Zustand und Lebenszyklus
In diesem Abschnitt erfahren Sie mehr über den Status und den Lebenszyklus von Lit.
Bundesland
Das Konzept von „Reactive Properties“ in Lit ist eine Mischung aus dem Status und den Attributen von React. Wenn sich reaktive Eigenschaften ändern, kann dies den Lebenszyklus der Komponente auslösen. Es gibt zwei Varianten von reaktiven Properties:
Öffentliche reaktive Eigenschaften
// React
import React from 'react';
class MyEl extends React.Component {
constructor(props) {
super(props)
this.state = {name: 'there'}
}
componentWillReceiveProps(nextProps) {
if (this.props.name !== nextProps.name) {
this.setState({name: nextProps.name})
}
}
}
// Lit (TS)
import {LitElement} from 'lit';
import {property} from 'lit/decorators.js';
class MyEl extends LitElement {
@property() name = 'there';
}
- Definiert durch
@property - Ähnlich wie die Props und der Status von React, aber veränderlich
- Öffentliche API, auf die von Nutzern der Komponente zugegriffen und die von ihnen festgelegt wird
Interner reaktiver Status
// React
import React from 'react';
class MyEl extends React.Component {
constructor(props) {
super(props)
this.state = {name: 'there'}
}
}
// Lit (TS)
import {LitElement} from 'lit';
import {state} from 'lit/decorators.js';
class MyEl extends LitElement {
@state() name = 'there';
}
- Definiert durch
@state - Ähnlich wie der Status in React, aber veränderlich
- Privater interner Status, auf den in der Regel von der Komponente oder von Unterklassen aus zugegriffen wird
Lifecycle
Der Lit-Lebenszyklus ähnelt dem von React, es gibt jedoch einige wichtige Unterschiede.
constructor
// React (js)
import React from 'react';
class MyEl extends React.Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
this._privateProp = 'private';
}
}
// Lit (ts)
class MyEl extends LitElement {
@property({type: Number}) counter = 0;
private _privateProp = 'private';
}
// Lit (js)
class MyEl extends LitElement {
static get properties() {
return { counter: {type: Number} }
}
constructor() {
this.counter = 0;
this._privateProp = 'private';
}
}
- Die beleuchtete Entsprechung ist ebenfalls
constructor. - Es muss nichts an den Super-Aufruf übergeben werden.
- Aufgerufen von (nicht vollständig):
document.createElementdocument.innerHTMLnew ComponentClass()- Wenn ein nicht aktualisierter Tag-Name auf der Seite vorhanden ist und die Definition mit
@customElementodercustomElements.definegeladen und registriert wird
- Funktioniert ähnlich wie
constructorin React
render
// React
render() {
return <div>Hello World</div>
}
// Lit
render() {
return html`<div>Hello World</div>`;
}
- Die beleuchtete Entsprechung ist ebenfalls
render. - Kann ein beliebiges renderbares Ergebnis zurückgeben, z.B.
TemplateResultoderstring. - Ähnlich wie bei React sollte
render()eine reine Funktion sein. - Wird auf dem Knoten gerendert, der von
createRenderRoot()zurückgegeben wird (standardmäßigShadowRoot).
componentDidMount
componentDidMount ähnelt einer Kombination aus den beiden Lit-Lebenszyklus-Callbacks firstUpdated und connectedCallback.
firstUpdated
import Chart from 'chart.js';
// React
componentDidMount() {
this._chart = new Chart(this.chartElRef.current, {...});
}
// Lit
firstUpdated() {
this._chart = new Chart(this.chartEl, {...});
}
- Wird beim ersten Rendern der Vorlage der Komponente in den Stamm der Komponente aufgerufen.
- Wird nur aufgerufen, wenn das Element verbunden ist, z. B. nicht über
document.createElement('my-component'), bis dieser Knoten an den DOM-Baum angehängt wird. - Hier können Sie die Komponente einrichten, wenn der DOM-Baum der Komponente gerendert werden muss.
- Im Gegensatz zu React führen
componentDidMount-Änderungen an reaktiven Eigenschaften infirstUpdatedzu einem erneuten Rendern. Der Browser fasst die Änderungen jedoch in der Regel im selben Frame zusammen. Wenn für diese Änderungen kein Zugriff auf das DOM des Root-Elements erforderlich ist, sollten sie in der Regel inwillUpdateerfolgen.
connectedCallback
// React
componentDidMount() {
this.window.addEventListener('resize', this.boundOnResize);
}
// Lit
connectedCallback() {
super.connectedCallback();
this.window.addEventListener('resize', this.boundOnResize);
}
- Wird aufgerufen, wenn das benutzerdefinierte Element in den DOM-Baum eingefügt wird.
- Im Gegensatz zu React-Komponenten werden benutzerdefinierte Elemente, wenn sie vom DOM getrennt werden, nicht zerstört und können daher mehrmals „verbunden“ werden.
firstUpdatedwird nicht noch einmal aufgerufen
- Nützlich, um das DOM neu zu initialisieren oder Event-Listener neu anzuhängen, die beim Trennen entfernt wurden
- Hinweis :
connectedCallbackkann vorfirstUpdatedaufgerufen werden. Beim ersten Aufruf ist das DOM möglicherweise nicht verfügbar.
componentDidUpdate
// React
componentDidUpdate(prevProps) {
if (this.props.title !== prevProps.title) {
this._chart.setTitle(this.props.title);
}
}
// Lit (ts)
updated(prevProps: PropertyValues<this>) {
if (prevProps.has('title')) {
this._chart.setTitle(this.title);
}
}
- Die wörtliche Übersetzung ist
updated(mit der englischen Vergangenheitsform von „update“). - Im Gegensatz zu React wird
updatedauch beim ersten Rendern aufgerufen. - Funktioniert ähnlich wie
componentDidUpdatein React
componentWillUnmount
// React
componentWillUnmount() {
this.window.removeEventListener('resize', this.boundOnResize);
}
// Lit
disconnectedCallback() {
super.disconnectedCallback();
this.window.removeEventListener('resize', this.boundOnResize);
}
- Die lit-Entsprechung ähnelt
disconnectedCallback. - Im Gegensatz zu React-Komponenten wird die Komponente nicht zerstört, wenn benutzerdefinierte Elemente vom DOM getrennt werden.
- Im Gegensatz zu
componentWillUnmountwirddisconnectedCallbacknach dem Entfernen des Elements aus dem Baum aufgerufen. - Das DOM innerhalb des Stammverzeichnisses ist weiterhin mit dem Unterbaum des Stammverzeichnisses verbunden.
- Nützlich, um Event-Listener und Speicherlecks zu bereinigen, damit der Browser die Komponente bereinigen kann
Training
import React from 'react';
import ReactDOM from 'react-dom';
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
Im obigen Beispiel gibt es eine einfache Uhr, die Folgendes tut:
- Es wird „Hello World!“ gerendert. Es ist“ und dann die Uhrzeit angezeigt wird.
- Die Uhr wird jede Sekunde aktualisiert.
- Wenn das Modul entfernt wird, wird das Intervall, in dem der Tick aufgerufen wird, gelöscht.
Beginnen Sie zuerst mit der Deklaration der Komponentenklasse:
// Lit (TS)
// some imports here are imported in advance
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';
@customElement('lit-clock')
class LitClock extends LitElement {
}
// Lit (JS)
// `html` is imported in advance
import {LitElement, html} from 'lit';
class LitClock extends LitElement {
}
customElements.define('lit-clock', LitClock);
Als Nächstes initialisieren Sie date und deklarieren es mit @state als interne reaktive Eigenschaft, da Nutzer der Komponente date nicht direkt festlegen.
// Lit (TS)
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';
@customElement('lit-clock')
class LitClock extends LitElement {
@state() // declares internal reactive prop
private date = new Date(); // initialization
}
// Lit (JS)
import {LitElement, html} from 'lit';
class LitClock extends LitElement {
static get properties() {
return {
// declares internal reactive prop
date: {state: true}
}
}
constructor() {
super();
// initialization
this.date = new Date();
}
}
customElements.define('lit-clock', LitClock);
Rendern Sie als Nächstes die Vorlage.
// Lit (JS & TS)
render() {
return html`
<div>
<h1>Hello, World!</h1>
<h2>It is ${this.date.toLocaleTimeString()}.</h2>
</div>
`;
}
Implementieren Sie nun die Methode „tick“.
tick() {
this.date = new Date();
}
Als Nächstes folgt die Implementierung von componentDidMount. Auch hier ist das Lit-Analogon eine Mischung aus firstUpdated und connectedCallback. Bei dieser Komponente ist für den Aufruf von tick mit setInterval kein Zugriff auf das DOM im Stammverzeichnis erforderlich. Außerdem wird das Intervall gelöscht, wenn das Element aus dem Dokumentbaum entfernt wird. Wenn es also wieder angehängt wird, muss das Intervall neu gestartet werden. connectedCallback ist hier also die bessere Wahl.
// Lit (TS)
@customElement('lit-clock')
class LitClock extends LitElement {
@state()
private date = new Date();
// initialize timerId for TS
private timerId = -1 as unknown as ReturnType<typeof setTimeout>;
connectedCallback() {
super.connectedCallback();
this.timerId = setInterval(
() => this.tick(),
1000
);
}
...
}
// Lit (JS)
constructor() {
super();
// initialization
this.date = new Date();
this.timerId = -1; // initialize timerId for JS
}
connectedCallback() {
super.connectedCallback();
this.timerId = setInterval(
() => this.tick(),
1000
);
}
Bereinigen Sie das Intervall schließlich so, dass der Tick nicht ausgeführt wird, nachdem das Element vom Dokumentbaum getrennt wurde.
// Lit (TS & JS)
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
Das Gesamtbild:
// Lit (TS)
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';
@customElement('lit-clock')
class LitClock extends LitElement {
@state()
private date = new Date();
private timerId = -1 as unknown as ReturnType<typeof setTimeout>;
connectedCallback() {
super.connectedCallback();
this.timerId = setInterval(
() => this.tick(),
1000
);
}
tick() {
this.date = new Date();
}
render() {
return html`
<div>
<h1>Hello, World!</h1>
<h2>It is ${this.date.toLocaleTimeString()}.</h2>
</div>
`;
}
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
}
// Lit (JS)
import {LitElement, html} from 'lit';
class LitClock extends LitElement {
static get properties() {
return {
date: {state: true}
}
}
constructor() {
super();
this.date = new Date();
}
connectedCallback() {
super.connectedCallback();
this.timerId = setInterval(
() => this.tick(),
1000
);
}
tick() {
this.date = new Date();
}
render() {
return html`
<div>
<h1>Hello, World!</h1>
<h2>It is ${this.date.toLocaleTimeString()}.</h2>
</div>
`;
}
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
}
customElements.define('lit-clock', LitClock);
7. Aufhänger
In diesem Abschnitt erfahren Sie, wie Sie React Hook-Konzepte in Lit übersetzen.
Konzepte von React-Hooks
Mit React-Hooks können Funktionskomponenten in den Status „eingehängt“ werden. Das bietet mehrere Vorteile.
- Sie vereinfachen die Wiederverwendung von zustandsbehafteter Logik.
- Komponente in kleinere Funktionen aufteilen
Außerdem wurden durch die Konzentration auf funktionsbasierte Komponenten bestimmte Probleme mit der klassenbasierten Syntax von React behoben, z. B.:
propsvonconstructornachsuperweiterleiten- Die unsaubere Initialisierung von Attributen in
constructor- Das war ein Grund, der vom React-Team zu diesem Zeitpunkt angegeben wurde, aber durch ES2019 behoben wurde.
- Probleme, die dadurch verursacht werden, dass
thisnicht mehr auf die Komponente verweist
React-Hooks-Konzepte in Lit
Wie im Abschnitt Komponenten und Attribute erwähnt, bietet Lit keine Möglichkeit, benutzerdefinierte Elemente aus einer Funktion zu erstellen. LitElement behebt jedoch die meisten der Hauptprobleme mit React-Klassenkomponenten. Beispiel:
// React (at the time of making hooks)
import React from 'react';
import ReactDOM from 'react-dom';
class MyEl extends React.Component {
constructor(props) {
super(props); // Leaky implementation
this.state = {count: 0};
this._chart = null; // Deemed messy
}
render() {
return (
<>
<div>Num times clicked {count}</div>
<button onClick={this.clickCallback}>click me</button>
</>
);
}
clickCallback() {
// Errors because `this` no longer refers to the component
this.setState({count: this.count + 1});
}
}
// Lit (ts)
class MyEl extends LitElement {
@property({type: Number}) count = 0; // No need for constructor to set state
private _chart = null; // Public class fields introduced to JS in 2019
render() {
return html`
<div>Num times clicked ${count}</div>
<button @click=${this.clickCallback}>click me</button>`;
}
private clickCallback() {
// No error because `this` refers to component
this.count++;
}
}
Wie geht Lit mit diesen Problemen um?
constructorakzeptiert keine Argumente.- Alle
@event-Bindungen werden automatisch anthisgebunden. thisbezieht sich in den meisten Fällen auf die Referenz des benutzerdefinierten Elements.- Klasseneigenschaften können jetzt als Klassenmitglieder instanziiert werden. Dadurch werden konstruktorbasierte Implementierungen bereinigt.
Reaktive Controller
Die primären Konzepte hinter Hooks sind in Lit als reaktive Controller vorhanden. Mit reaktiven Controller-Mustern können Sie zustandsbehaftete Logik freigeben, Komponenten in kleinere, modulare Teile aufteilen und in den Aktualisierungszyklus eines Elements eingreifen.
Ein reaktiver Controller ist eine Objektschnittstelle, die in den Aktualisierungszyklus eines Controller-Hosts wie LitElement eingebunden werden kann.
Der Lebenszyklus eines ReactiveController und eines reactiveControllerHost sieht so aus:
interface ReactiveController {
hostConnected(): void;
hostUpdate(): void;
hostUpdated(): void;
hostDisconnected(): void;
}
interface ReactiveControllerHost {
addController(controller: ReactiveController): void;
removeController(controller: ReactiveController): void;
requestUpdate(): void;
readonly updateComplete: Promise<boolean>;
}
Wenn Sie einen reaktiven Controller erstellen und ihn mit addController an einen Host anhängen, wird der Lebenszyklus des Controllers zusammen mit dem des Hosts aufgerufen. Erinnern Sie sich beispielsweise an das Uhrenbeispiel aus dem Abschnitt Status und Lebenszyklus:
import React from 'react';
import ReactDOM from 'react-dom';
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
Im obigen Beispiel gibt es eine einfache Uhr, die Folgendes tut:
- Es wird „Hello World!“ gerendert. Es ist“ und dann die Uhrzeit angezeigt wird.
- Die Uhr wird jede Sekunde aktualisiert.
- Wenn das Modul entfernt wird, wird das Intervall, in dem der Tick aufgerufen wird, gelöscht.
Komponentengerüst erstellen
Beginnen Sie mit der Deklaration der Komponentenklasse und fügen Sie die render-Funktion hinzu.
// Lit (TS) - index.ts
import {LitElement, html} from 'lit';
import {customElement} from 'lit/decorators.js';
@customElement('my-element')
class MyElement extends LitElement {
render() {
return html`
<div>
<h1>Hello, world!</h1>
<h2>It is ${'time to get Lit'}.</h2>
</div>
`;
}
}
// Lit (JS) - index.js
import {LitElement, html} from 'lit';
class MyElement extends LitElement {
render() {
return html`
<div>
<h1>Hello, world!</h1>
<h2>It is ${'time to get Lit'}.</h2>
</div>
`;
}
}
customElements.define('my-element', MyElement);
Controller erstellen
Wechseln Sie jetzt zu clock.ts und erstellen Sie eine Klasse für ClockController und richten Sie constructor ein:
// Lit (TS) - clock.ts
import {ReactiveController, ReactiveControllerHost} from 'lit';
export class ClockController implements ReactiveController {
private readonly host: ReactiveControllerHost;
constructor(host: ReactiveControllerHost) {
this.host = host;
host.addController(this);
}
hostConnected() {
}
private tick() {
}
hostDisconnected() {
}
}
// Lit (JS) - clock.js
export class ClockController {
constructor(host) {
this.host = host;
host.addController(this);
}
hostConnected() {
}
tick() {
}
hostDisconnected() {
}
}
Ein reaktiver Controller kann auf beliebige Weise erstellt werden, solange er die ReactiveController-Schnittstelle verwendet. Das Lit-Team bevorzugt jedoch für die meisten grundlegenden Fälle die Verwendung einer Klasse mit einem constructor, das eine ReactiveControllerHost-Schnittstelle sowie alle anderen Eigenschaften akzeptieren kann, die zum Initialisieren des Controllers erforderlich sind.
Jetzt müssen Sie die React-Lifecycle-Callbacks in Controller-Callbacks übersetzen. Kurzum:
componentDidMount- Zu
connectedCallbackvon LitElement - Zum
hostConnecteddes Controllers
- Zu
ComponentWillUnmount- Zu
disconnectedCallbackvon LitElement - Zum
hostDisconnecteddes Controllers
- Zu
Weitere Informationen zum Übersetzen des React-Lebenszyklus in den Lit-Lebenszyklus finden Sie im Abschnitt State & Lifecycle.
Implementieren Sie als Nächstes den hostConnected-Callback und die tick-Methoden und bereinigen Sie das Intervall in hostDisconnected, wie im Beispiel im Abschnitt Status und Lebenszyklus beschrieben.
// Lit (TS) - clock.ts
export class ClockController implements ReactiveController {
private readonly host: ReactiveControllerHost;
private interval = 0 as unknown as ReturnType<typeof setTimeout>;
date = new Date();
constructor(host: ReactiveControllerHost) {
this.host = host;
host.addController(this);
}
hostConnected() {
this.interval = setInterval(() => this.tick(), 1000);
}
private tick() {
this.date = new Date();
}
hostDisconnected() {
clearInterval(this.interval);
}
}
// Lit (JS) - clock.js
export class ClockController {
interval = 0;
host;
date = new Date();
constructor(host) {
this.host = host;
host.addController(this);
}
hostConnected() {
this.interval = setInterval(() => this.tick(), 1000);
}
tick() {
this.date = new Date();
}
hostDisconnected() {
clearInterval(this.interval);
}
}
Controller verwenden
Wenn Sie den Zeitgeber-Controller verwenden möchten, importieren Sie den Controller und aktualisieren Sie die Komponente in index.ts oder index.js.
// Lit (TS) - index.ts
import {LitElement, html, ReactiveController, ReactiveControllerHost} from 'lit';
import {customElement} from 'lit/decorators.js';
import {ClockController} from './clock.js';
@customElement('my-element')
class MyElement extends LitElement {
private readonly clock = new ClockController(this); // Instantiate
render() {
// Use controller
return html`
<div>
<h1>Hello, world!</h1>
<h2>It is ${this.clock.date.toLocaleTimeString()}.</h2>
</div>
`;
}
}
// Lit (JS) - index.js
import {LitElement, html} from 'lit';
import {ClockController} from './clock.js';
class MyElement extends LitElement {
clock = new ClockController(this); // Instantiate
render() {
// Use controller
return html`
<div>
<h1>Hello, world!</h1>
<h2>It is ${this.clock.date.toLocaleTimeString()}.</h2>
</div>
`;
}
}
customElements.define('my-element', MyElement);
Um den Controller zu verwenden, müssen Sie ihn instanziieren, indem Sie eine Referenz zum Controller-Host (der <my-element>-Komponente) übergeben, und ihn dann in der render-Methode verwenden.
Neu rendern im Controller auslösen
Die Uhrzeit wird angezeigt, aber nicht aktualisiert. Das liegt daran, dass der Controller das Datum jede Sekunde festlegt, der Host es aber nicht aktualisiert. Das liegt daran, dass sich date in der Klasse ClockController und nicht mehr in der Komponente ändert. Das bedeutet, dass der Host nach dem Festlegen des date auf dem Controller angewiesen werden muss, seinen Aktualisierungszyklus mit host.requestUpdate() auszuführen.
// Lit (TS & JS) - clock.ts / clock.js
private tick() {
this.date = new Date();
this.host.requestUpdate();
}
Die Uhr sollte jetzt ticken.
Einen detaillierteren Vergleich gängiger Anwendungsfälle mit Hooks finden Sie im Abschnitt Erweiterte Themen – Hooks.
8. Kinder
In diesem Abschnitt erfahren Sie, wie Sie mit Slots Kinder in Lit verwalten.
Slots & Kinder
Mit Slots können Sie Komponenten verschachteln.
In React werden untergeordnete Elemente über Props vererbt. Der Standardslot ist props.children und die Funktion render definiert, wo sich der Standardslot befindet. Beispiel:
const MyArticle = (props) => {
return <article>{props.children}</article>;
};
props.children sind React-Komponenten und keine HTML-Elemente.
In Lit werden untergeordnete Elemente in der Render-Funktion mit Slot-Elementen zusammengesetzt. Beachten Sie, dass untergeordnete Elemente nicht auf dieselbe Weise wie in React übernommen werden. In Lit sind Kinder HTMLElements, die an Slots angehängt sind. Diese Anlage wird als Projektion bezeichnet.
@customElement("my-article")
export class MyArticle extends LitElement {
render() {
return html`
<article>
<slot></slot>
</article>
`;
}
}
Mehrere Slots
In React ist das Hinzufügen mehrerer Slots im Grunde dasselbe wie das Übernehmen weiterer Attribute.
const MyArticle = (props) => {
return (
<article>
<header>
{props.headerChildren}
</header>
<section>
{props.sectionChildren}
</section>
</article>
);
};
Wenn Sie weitere <slot>-Elemente hinzufügen, werden auch mehr Slots in Lit erstellt. Mit dem Attribut name sind mehrere Slots definiert: <slot name="slot-name">. So können Kinder angeben, welchem Slot sie zugewiesen werden.
@customElement("my-article")
export class MyArticle extends LitElement {
render() {
return html`
<article>
<header>
<slot name="headerChildren"></slot>
</header>
<section>
<slot name="sectionChildren"></slot>
</section>
</article>
`;
}
}
Standardinhalte für Slots
In Slots wird der zugehörige Unterbaum angezeigt, wenn keine Knoten in diesen Slot projiziert werden. Wenn Knoten in einen Slot projiziert werden, wird der zugehörige Unterbaum nicht angezeigt. Stattdessen werden die projizierten Knoten angezeigt.
@customElement("my-element")
export class MyElement extends LitElement {
render() {
return html`
<section>
<div>
<slot name="slotWithDefault">
<p>
This message will not be rendered when children are attached to this slot!
<p>
</slot>
</div>
</section>
`;
}
}
Kindern Zeitblöcke zuweisen
In React werden untergeordnete Elemente über die Eigenschaften einer Komponente Slots zugewiesen. Im Beispiel unten werden React-Elemente an die Attribute headerChildren und sectionChildren übergeben.
const MyNewsArticle = () => {
return (
<MyArticle
headerChildren={<h3>Extry, Extry! Read all about it!</h3>}
sectionChildren={<p>Children are props in React!</p>}
/>
);
};
In Lit werden untergeordnete Elemente mithilfe des Attributs slot Slots zugewiesen.
@customElement("my-news-article")
export class MyNewsArticle extends LitElement {
render() {
return html`
<my-article>
<h3 slot="headerChildren">
Extry, Extry! Read all about it!
</h3>
<p slot="sectionChildren">
Children are composed with slots in Lit!
</p>
</my-article>
`;
}
}
Wenn es keinen Standardslot (z.B. <slot>) und keinen Slot mit einem name-Attribut (z.B. <slot name="foo">) gibt, das mit dem slot-Attribut der untergeordneten Elemente des benutzerdefinierten Elements (z.B. <div slot="foo">) übereinstimmt, wird dieser Knoten nicht projiziert und nicht angezeigt.
9. Refs
Gelegentlich muss ein Entwickler auf die API eines HTMLElement zugreifen.
In diesem Abschnitt erfahren Sie, wie Sie Elementreferenzen in Lit abrufen.
React-Referenzen
Eine React-Komponente wird in eine Reihe von Funktionsaufrufen transpiliert, die beim Aufrufen ein virtuelles DOM erstellen. Dieses virtuelle DOM wird von ReactDOM interpretiert und rendert HTMLElements.
In React sind Refs Speicherplatz im Arbeitsspeicher, der ein generiertes HTMLElement enthält.
const RefsExample = (props) => {
const inputRef = React.useRef(null);
const onButtonClick = React.useCallback(() => {
inputRef.current?.focus();
}, [inputRef]);
return (
<div>
<input type={"text"} ref={inputRef} />
<br />
<button onClick={onButtonClick}>
Click to focus on the input above!
</button>
</div>
);
};
Im obigen Beispiel führt die React-Komponente Folgendes aus:
- Leeres Texteingabefeld und Button mit Text rendern
- Fokus auf das Eingabefeld setzen, wenn auf die Schaltfläche geklickt wird
Nach dem ersten Rendern setzt React inputRef.current über das Attribut ref auf das generierte HTMLInputElement.
„Referenzen“ mit @query beleuchten
Lit ist nah am Browser angesiedelt und bietet eine sehr dünne Abstraktion über native Browserfunktionen.
Das React-Äquivalent zu refs in Lit ist das HTMLElement, das von den Dekoratoren @query und @queryAll zurückgegeben wird.
@customElement("my-element")
export class MyElement extends LitElement {
@query('input') // Define the query
inputEl!: HTMLInputElement; // Declare the prop
// Declare the click event listener
onButtonClick() {
// Use the query to focus
this.inputEl.focus();
}
render() {
return html`
<input type="text">
<br />
<!-- Bind the click listener -->
<button @click=${this.onButtonClick}>
Click to focus on the input above!
</button>
`;
}
}
Im obigen Beispiel führt die Lit-Komponente Folgendes aus:
- Definiert eine Property für
MyElementmithilfe des Decorators@query(erstellt einen Getter für einHTMLInputElement). - Deklariert und fügt einen Click-Event-Callback namens
onButtonClickhinzu. - Fokus auf die Eingabe bei Schaltflächenklick
In JavaScript führen die Decorators @query und @queryAll die Funktionen querySelector bzw. querySelectorAll aus. Dies ist das JavaScript-Äquivalent von @query('input') inputEl!: HTMLInputElement;
get inputEl() {
return this.renderRoot.querySelector('input');
}
Nachdem die Lit-Komponente die Vorlage der render-Methode an den Stamm von my-element übergeben hat, kann inputEl mit dem @query-Decorator das erste input-Element zurückgeben, das im Renderstamm gefunden wird. null wird zurückgegeben, wenn das angegebene Element nicht gefunden werden kann.@query
Wenn es mehrere input-Elemente im Render-Root gäbe, würde @queryAll eine Liste von Knoten zurückgeben.
10. Vermittlungsstatus
In diesem Abschnitt erfahren Sie, wie Sie den Status zwischen Komponenten in Lit vermitteln.
Wiederverwendbare Komponenten
React ahmt funktionale Rendering-Pipelines mit Top-down-Datenfluss nach. Eltern vermitteln Kindern durch Requisiten einen Zustand. Kinder kommunizieren mit Eltern über Callbacks in den Attributen.
const CounterButton = (props) => {
const label = props.step < 0
? `- ${-1 * props.step}`
: `+ ${props.step}`;
return (
<button
onClick={() =>
props.addToCounter(props.step)}>{label}</button>
);
};
Im obigen Beispiel führt eine React-Komponente Folgendes aus:
- Erstellt ein Label basierend auf dem Wert
props.step. - Rendert einen Button mit „+step“ oder „-step“ als Label.
- Aktualisiert die übergeordnete Komponente, indem bei einem Klick
props.addToCountermitprops.stepals Argument aufgerufen wird.
Obwohl es möglich ist, Callbacks in Lit zu übergeben, sind die herkömmlichen Muster anders. Die React-Komponente im obigen Beispiel könnte als Lit-Komponente im folgenden Beispiel geschrieben werden:
@customElement('counter-button')
export class CounterButton extends LitElement {
@property({type: Number}) step: number = 0;
onClick() {
const event = new CustomEvent('update-counter', {
bubbles: true,
detail: {
step: this.step,
}
});
this.dispatchEvent(event);
}
render() {
const label = this.step < 0
? `- ${-1 * this.step}` // "- 1"
: `+ ${this.step}`; // "+ 1"
return html`
<button @click=${this.onClick}>${label}</button>
`;
}
}
Im obigen Beispiel führt eine Lit-Komponente Folgendes aus:
- Reaktive Property
steperstellen - Ein benutzerdefiniertes Ereignis namens
update-counterwird ausgelöst, das denstep-Wert des Elements bei einem Klick enthält.
Browserereignisse werden von untergeordneten an übergeordnete Elemente weitergegeben. Mit Ereignissen können Kinder Interaktionsereignisse und Statusänderungen übertragen. In React wird der Status grundsätzlich in die entgegengesetzte Richtung übergeben. Daher ist es ungewöhnlich, dass React-Komponenten Ereignisse auf dieselbe Weise wie Lit-Komponenten senden und darauf reagieren.
Zustandsorientierte Komponenten
In React werden häufig Hooks verwendet, um den Status zu verwalten. Eine MyCounter-Komponente kann durch Wiederverwendung der CounterButton-Komponente erstellt werden. Beachten Sie, dass addToCounter an beide Instanzen von CounterButton übergeben wird.
const MyCounter = (props) => {
const [counterSum, setCounterSum] = React.useState(0);
const addToCounter = useCallback(
(step) => {
setCounterSum(counterSum + step);
},
[counterSum, setCounterSum]
);
return (
<div>
<h3>Σ: {counterSum}</h3>
<CounterButton
step={-1}
addToCounter={addToCounter} />
<CounterButton
step={1}
addToCounter={addToCounter} />
</div>
);
};
Das Beispiel oben führt Folgendes aus:
- Erstellt einen
count-Status. - Erstellt einen Callback, der eine Zahl zu einem
count-Zustand hinzufügt. CounterButtonverwendetaddToCounter, umcountbei jedem Klick umstepzu aktualisieren.
Eine ähnliche Implementierung von MyCounter ist in Lit möglich. Beachten Sie, dass addToCounter nicht an counter-button übergeben wird. Stattdessen wird der Callback als Event-Listener an das @update-counter-Ereignis eines übergeordneten Elements gebunden.
@customElement("my-counter")
export class MyCounter extends LitElement {
@property({type: Number}) count = 0;
addToCounter(e: CustomEvent<{step: number}>) {
// Get step from detail of event or via @query
this.count += e.detail.step;
}
render() {
return html`
<div @update-counter="${this.addToCounter}">
<h3>Σ ${this.count}</h3>
<counter-button step="-1"></counter-button>
<counter-button step="1"></counter-button>
</div>
`;
}
}
Das Beispiel oben führt Folgendes aus:
- Erstellt ein reaktives Attribut namens
count, das die Komponente aktualisiert, wenn der Wert geändert wird. - Bindet den
addToCounter-Callback an den@update-counter-Event-Listener - Aktualisiert
count, indem der Wert ausdetail.stepdes Ereignissesupdate-counterhinzugefügt wird. - Legt den
step-Wert voncounter-buttonüber das Attributstepfest.
Es ist üblicher, reaktive Eigenschaften in Lit zu verwenden, um Änderungen von übergeordneten an untergeordnete Elemente zu übertragen. Ebenso empfiehlt es sich, das Ereignissystem des Browsers zu verwenden, um Details von unten nach oben weiterzugeben.
Dieser Ansatz entspricht den Best Practices und dem Ziel von Lit, plattformübergreifende Unterstützung für Webkomponenten zu bieten.
11. Stile
In diesem Abschnitt erfahren Sie mehr über das Styling in Lit.
Stile
Lit bietet mehrere Möglichkeiten zum Formatieren von Elementen sowie eine integrierte Lösung.
Inline-Formatierungen
Lit unterstützt Inline-Stile sowie die Bindung an sie.
import {LitElement, html, css} from 'lit';
import {customElement} from 'lit/decorators.js';
@customElement('my-element')
class MyElement extends LitElement {
render() {
return html`
<div>
<h1 style="color:orange;">This text is orange</h1>
<h1 style="color:rebeccapurple;">This text is rebeccapurple</h1>
</div>
`;
}
}
Im obigen Beispiel gibt es zwei Überschriften mit jeweils einem Inline-Stil.
Importieren Sie nun einen Rahmen aus border-color.js und binden Sie ihn an den orangefarbenen Text:
...
import borderColor from './border-color.js';
...
html`
...
<h1 style="color:orange;${borderColor}">This text is orange</h1>
...`
Es kann etwas mühsam sein, den Stilstring jedes Mal neu zu berechnen. Daher bietet Lit eine Direktive, die dabei hilft.
styleMap
Die styleMap-Anweisung erleichtert das Festlegen von Inline-Stilen mit JavaScript. Beispiel:
import {LitElement, html, css} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {styleMap} from 'lit/directives/style-map.js';
@customElement('my-element')
class MyElement extends LitElement {
@property({type: String})
color = '#000'
render() {
// Define the styleMap
const headerStyle = styleMap({
'border-color': this.color,
});
return html`
<div>
<h1
style="border-style:solid;
<!-- Use the styleMap -->
border-width:2px;${headerStyle}">
This div has a border color of ${this.color}
</h1>
<input
type="color"
@input=${e => (this.color = e.target.value)}
value="#000">
</div>
`;
}
}
Das obige Beispiel führt Folgendes aus:
- Zeigt ein
h1mit einem Rahmen und einer Farbauswahl an - Ändert
border-colorin den Wert aus der Farbauswahl.
Außerdem gibt es styleMap, mit dem die Formatierungen des h1 festgelegt werden. styleMap folgt einer Syntax, die der Attributbindungssyntax von style in React ähnelt.
CSSResult
Es wird empfohlen, Komponenten mit dem Tagged Template Literal css zu gestalten.
import {LitElement, html, css} from 'lit';
import {customElement} from 'lit/decorators.js';
const ORANGE = css`orange`;
@customElement('my-element')
class MyElement extends LitElement {
static styles = [
css`
#orange {
color: ${ORANGE};
}
#purple {
color: rebeccapurple;
}
`
];
render() {
return html`
<div>
<h1 id="orange">This text is orange</h1>
<h1 id="purple">This text is rebeccapurple</h1>
</div>
`;
}
}
Das obige Beispiel führt Folgendes aus:
- Deklariert ein CSS-Tagged Template Literal mit einer Bindung
- Legt die Farben von zwei
h1s mit IDs fest.
Vorteile der Verwendung des Vorlagen-Tags css:
- Einmal pro Klasse im Vergleich zu einmal pro Instanz geparst
- Implementiert mit Blick auf die Wiederverwendbarkeit von Modulen
- Stile lassen sich ganz einfach in separate Dateien aufteilen.
- Kompatibel mit dem CSS-Polyfill für benutzerdefinierte Eigenschaften
Beachten Sie außerdem den <style>-Tag in index.html:
<!-- index.html -->
<style>
h1 {
color: red !important;
}
</style>
In Lit werden die Stile Ihrer Komponenten auf ihre Roots beschränkt. Das bedeutet, dass Stile nicht nach innen und außen durchsickern. Um Stile an Komponenten zu übergeben, empfiehlt das Lit-Team die Verwendung von benutzerdefinierten CSS-Eigenschaften, da diese die Lit-Stilbereichsbegrenzung durchdringen können.
Stil-Tags
Sie können <style>-Tags auch einfach inline in Ihre Vorlagen einfügen. Der Browser dedupliziert diese Style-Tags. Wenn Sie sie jedoch in Ihre Vorlagen einfügen, werden sie pro Komponenteninstanz und nicht pro Klasse geparst, wie es bei der mit css getaggten Vorlage der Fall ist. Außerdem ist die Deduplizierung von CSSResults im Browser viel schneller.
Link-Tags
Sie können auch ein <link rel="stylesheet"> in Ihrer Vorlage für Stile verwenden. Dies wird jedoch nicht empfohlen, da es zu einem anfänglichen Aufblitzen von nicht formatierten Inhalten (Flash of Unstyled Content, FOUC) kommen kann.
12. Weiterführende Informationen (optional)
JSX und Vorlagen
Lit & virtuelles DOM
Lit-html enthält kein herkömmliches virtuelles DOM, in dem jeder einzelne Knoten verglichen wird. Stattdessen werden Leistungsmerkmale genutzt, die in der ES2015-Spezifikation für Tagged Template Literals (markierte Vorlagenliterale) enthalten sind. Markierte Vorlagenliterale sind Vorlagenliteral-Strings, an die Tag-Funktionen angehängt sind.
Hier ist ein Beispiel für ein Template-Literal:
const str = 'string';
console.log(`This is a template literal ${str}`);
Hier ist ein Beispiel für ein getaggtes Template-Literal:
const tag = (strings, ...values) => ({strings, values});
const f = (x) => tag`hello ${x} how are you`;
console.log(f('world')); // {strings: ["hello ", " how are you"], values: ["world"]}
console.log(f('world').strings === f(1 + 2).strings); // true
Im obigen Beispiel ist das Tag die Funktion tag und die Funktion f gibt einen Aufruf eines getaggten Vorlagenliterals zurück.
Ein Großteil der Leistungssteigerung in Lit beruht darauf, dass die an die Tag-Funktion übergebenen String-Arrays denselben Zeiger haben (wie im zweiten console.log gezeigt). Der Browser erstellt nicht bei jedem Aufruf der Tag-Funktion ein neues strings-Array, da er dasselbe Template-Literal verwendet (d.h. an derselben Stelle im AST). Die Bindung, das Parsen und das Vorlagencaching von Lit können diese Funktionen also ohne großen Laufzeit-Diffing-Overhead nutzen.
Dieses integrierte Browserverhalten von getaggten Template-Literalen bietet Lit einen erheblichen Leistungsvorteil. Bei den meisten herkömmlichen virtuellen DOMs wird der Großteil der Arbeit in JavaScript erledigt. Die meisten Differenzierungen von getaggten Template-Literalen werden jedoch im C++-Code des Browsers durchgeführt.
Wenn Sie mit HTML-getaggten Template-Literalen in React oder Preact beginnen möchten, empfiehlt das Lit-Team die htm-Bibliothek.
Wie bei der Google Codelabs-Website und mehreren Online-Code-Editoren werden Sie jedoch feststellen, dass die Syntaxhervorhebung für getaggte Template-Literale nicht sehr verbreitet ist. Einige IDEs und Texteditoren unterstützen sie standardmäßig, z. B. Atom und der Codeblock-Highlighter von GitHub. Das Lit-Team arbeitet auch sehr eng mit der Community zusammen, um Projekte wie das lit-plugin zu pflegen. Das ist ein VS Code-Plug-in, das Syntaxhervorhebung, Typüberprüfung und IntelliSense für Ihre Lit-Projekte bietet.
Lit & JSX + React DOM
JSX wird nicht im Browser ausgeführt, sondern verwendet einen Präprozessor, um JSX in JavaScript-Funktionsaufrufe zu konvertieren (in der Regel über Babel).
Babel transformiert beispielsweise Folgendes:
const element = <div className="title">Hello World!</div>;
ReactDOM.render(element, mountNode);
in Folgendes ändern:
const element = React.createElement('div', {className: 'title'}, 'Hello World!');
ReactDOM.render(element, mountNode);
React DOM nimmt dann die React-Ausgabe und übersetzt sie in das tatsächliche DOM – mit Eigenschaften, Attributen, Ereignis-Listenern usw.
Lit-html verwendet getaggte Template-Literale, die ohne Transpilierung oder Vorprozessor im Browser ausgeführt werden können. Um mit Lit zu beginnen, benötigen Sie also nur eine HTML-Datei, ein ES-Modul-Script und einen Server. Hier ist ein Skript, das vollständig im Browser ausgeführt werden kann:
<!DOCTYPE html>
<html>
<head>
<script type="module">
import {html, render} from 'https://cdn.skypack.dev/lit';
render(
html`<div>Hello World!</div>`,
document.querySelector('.root')
)
</script>
</head>
<body>
<div class="root"></div>
</body>
</html>
Da das Templating-System von Lit, lit-html, kein herkömmliches virtuelles DOM verwendet, sondern die DOM API direkt nutzt, ist Lit 2 mit weniger als 5 KB minimiert und gezippt im Vergleich zu React (2,8 KB) + react-dom (39,4 KB) mit 40 KB minimiert und gezippt.
Ereignisse
React verwendet ein synthetisches Ereignissystem. Das bedeutet, dass in „react-dom“ jedes Ereignis definiert werden muss, das in jeder Komponente verwendet wird, und dass für jeden Knotentyp ein entsprechendes CamelCase-Ereignis bereitgestellt werden muss. Daher gibt es in JSX keine Methode zum Definieren eines Event-Listeners für ein benutzerdefiniertes Ereignis. Entwickler müssen ref verwenden und dann imperativ einen Listener anwenden. Dies führt zu einer schlechten Entwicklererfahrung bei der Integration von Bibliotheken, die nicht für React entwickelt wurden, da ein React-spezifischer Wrapper geschrieben werden muss.
Lit-html greift direkt auf das DOM zu und verwendet native Ereignisse. Das Hinzufügen von Event-Listenern ist daher so einfach wie @event-name=${eventNameListener}. Das bedeutet, dass weniger Laufzeit-Parsing für das Hinzufügen von Event-Listenern und das Auslösen von Ereignissen erforderlich ist.
Komponenten und Attribute
React-Komponenten und benutzerdefinierte Elemente
LitElement verwendet benutzerdefinierte Elemente, um seine Komponenten zu verpacken. Benutzerdefinierte Elemente bringen einige Kompromisse zwischen React-Komponenten mit sich, wenn es um die Komponentisierung geht (Status und Lebenszyklus werden im Abschnitt Status und Lebenszyklus näher erläutert).
Einige Vorteile von benutzerdefinierten Elementen als Komponentensystem:
- Sie sind im Browser integriert und erfordern keine zusätzlichen Tools.
- Passen Sie jede Browser-API von
innerHTMLunddocument.createElementbisquerySelectoran. - Kann in der Regel in allen Frameworks verwendet werden
- Kann mit
customElements.defineverzögert registriert und das DOM „hydriert“ werden.
Einige Nachteile von benutzerdefinierten Elementen im Vergleich zu React-Komponenten:
- Es ist nicht möglich, ein benutzerdefiniertes Element zu erstellen, ohne eine Klasse zu definieren (daher keine JSX-ähnlichen funktionalen Komponenten).
- Muss ein schließendes Tag
- enthalten.
- Hinweis:Trotz der Entwicklerfreundlichkeit bedauern Browseranbieter in der Regel die Spezifikation für selbstschließende Tags. Daher enthalten neuere Spezifikationen in der Regel keine selbstschließenden Tags.
- Fügt dem DOM-Baum einen zusätzlichen Knoten hinzu, was zu Layoutproblemen führen kann
- Muss über JavaScript registriert werden
Lit verwendet benutzerdefinierte Elemente anstelle eines eigenen Elementsystems, da benutzerdefinierte Elemente in den Browser integriert sind. Das Lit-Team ist der Ansicht, dass die frameworkübergreifenden Vorteile die Vorteile einer Komponentenabstraktionsschicht überwiegen. Tatsächlich hat das Lit-Team mit seinen Bemühungen im Bereich „lit-ssr“ die Hauptprobleme bei der JavaScript-Registrierung behoben. Einige Unternehmen wie GitHub nutzen außerdem die Lazy Registration von benutzerdefinierten Elementen, um Seiten schrittweise mit optionalen Elementen zu erweitern.
Daten an benutzerdefinierte Elemente übergeben
Ein häufiges Missverständnis bei benutzerdefinierten Elementen ist, dass Daten nur als Strings übergeben werden können. Dieser Irrtum beruht wahrscheinlich darauf, dass Elementattribute nur als Strings geschrieben werden können. Es stimmt zwar, dass Lit String-Attribute in die definierten Typen umwandelt, benutzerdefinierte Elemente können aber auch komplexe Daten als Eigenschaften akzeptieren.
Beispiel:
// data-test.ts
import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('data-test')
class DataTest extends LitElement {
@property({type: Number})
num = 0;
@property({attribute: false})
data = {a: 0, b: null, c: [html`<div>hello</div>`, html`<div>world</div>`]}
render() {
return html`
<div>num + 1 = ${this.num + 1}</div>
<div>data.a = ${this.data.a}</div>
<div>data.b = ${this.data.b}</div>
<div>data.c = ${this.data.c}</div>`;
}
}
Es wird eine primitive reaktive Property num definiert, die den Stringwert eines Attributs in ein number konvertiert. Anschließend wird mit attribute:false eine komplexe Datenstruktur eingeführt, die die Attributverarbeitung von Lit deaktiviert.
So übergeben Sie Daten an dieses benutzerdefinierte Element:
<head>
<script type="module">
import './data-test.js'; // loads element definition
import {html} from './data-test.js';
const el = document.querySelector('data-test');
el.data = {
a: 5,
b: null,
c: [html`<div>foo</div>`,html`<div>bar</div>`]
};
</script>
</head>
<body>
<data-test num="5"></data-test>
</body>
Zustand und Lebenszyklus
Andere React-Lebenszyklus-Callbacks
static getDerivedStateFromProps
In Lit gibt es kein Äquivalent, da „props“ und „state“ beides dieselben Klassenattribute sind.
shouldComponentUpdate
- Die entsprechende Literatur ist
shouldUpdate. - Wird beim ersten Rendern aufgerufen, anders als bei React
- Funktioniert ähnlich wie
shouldComponentUpdatein React
getSnapshotBeforeUpdate
In Lit ähnelt getSnapshotBeforeUpdate sowohl update als auch willUpdate.
willUpdate
- Angerufen vor dem
update - Im Gegensatz zu
getSnapshotBeforeUpdatewirdwillUpdatevorrenderaufgerufen. - Änderungen an reaktiven Eigenschaften in
willUpdatelösen den Aktualisierungszyklus nicht noch einmal aus. - Guter Ort, um Attributwerte zu berechnen, die von anderen Attributen abhängen und im restlichen Aktualisierungsprozess verwendet werden
- Diese Methode wird auf dem Server in SSR aufgerufen. Daher wird davon abgeraten, hier auf das DOM zuzugreifen.
update
- Angerufen nach dem
willUpdate - Im Gegensatz zu
getSnapshotBeforeUpdatewirdupdatevorrenderaufgerufen. - Änderungen an reaktiven Attributen in
updatelösen den Aktualisierungszyklus nicht neu aus, wenn sie vor dem Aufrufen vonsuper.updategeändert werden. - Guter Ort, um Informationen aus dem DOM rund um die Komponente zu erfassen, bevor die gerenderte Ausgabe im DOM gespeichert wird
- Diese Methode wird beim serverseitigen Rendern nicht auf dem Server aufgerufen.
Andere Lit-Lebenszyklus-Callbacks
Es gibt mehrere Lebenszyklus-Callbacks, die im vorherigen Abschnitt nicht erwähnt wurden, da es in React kein Analogon dazu gibt. Diese sind:
attributeChangedCallback
Sie wird aufgerufen, wenn sich eine der observedAttributes des Elements ändert. Sowohl observedAttributes als auch attributeChangedCallback sind Teil der Spezifikation für benutzerdefinierte Elemente und werden von Lit im Hintergrund implementiert, um eine Attribut-API für Lit-Elemente bereitzustellen.
adoptedCallback
Wird aufgerufen, wenn die Komponente in ein neues Dokument verschoben wird, z.B. von der HTMLTemplateElement-Datei documentFragment in die Hauptdatei document. Dieser Callback ist auch Teil der Spezifikation für benutzerdefinierte Elemente und sollte nur für erweiterte Anwendungsfälle verwendet werden, wenn die Komponente Dokumente ändert.
Andere Methoden und Eigenschaften für den Lebenszyklus
Diese Methoden und Eigenschaften sind Klassenmember, die Sie aufrufen, überschreiben oder abwarten können, um den Lebenszyklusprozess zu bearbeiten.
updateComplete
Dies ist ein Promise, der aufgelöst wird, wenn das Element aktualisiert wurde, da die Update- und Render-Lifecycles asynchron sind. Beispiel:
async nextButtonClicked() {
this.step++;
// Wait for the next "step" state to render
await this.updateComplete;
this.dispatchEvent(new Event('step-rendered'));
}
getUpdateComplete
Diese Methode sollte überschrieben werden, um anzupassen, wann updateComplete aufgelöst wird. Das ist häufig der Fall, wenn eine Komponente eine untergeordnete Komponente rendert und ihre Renderzyklen synchronisiert werden müssen, z. B.:
class MyElement extends LitElement {
...
async getUpdateComplete() {
await super.getUpdateComplete();
await this.myChild.updateComplete;
}
}
performUpdate
Mit dieser Methode werden die Update-Lifecycle-Callbacks aufgerufen. Dies ist in der Regel nur in seltenen Fällen erforderlich, in denen die Aktualisierung synchron erfolgen muss oder eine benutzerdefinierte Planung erforderlich ist.
hasUpdated
Diese Eigenschaft ist true, wenn die Komponente mindestens einmal aktualisiert wurde.
isConnected
Diese Eigenschaft ist Teil der Spezifikation für benutzerdefinierte Elemente und wird auf true gesetzt, wenn das Element derzeit an den Hauptdokumentbaum angehängt ist.
Visualisierung des Lebenszyklus von Literatur-Updates
Der Aktualisierungslebenszyklus besteht aus drei Teilen:
- Vor dem Update
- Aktualisieren
- Nach der Aktualisierung
Vor dem Update

Nach dem requestUpdate wird ein geplantes Update erwartet.
Aktualisieren

Nach der Aktualisierung

Aufhänger
Warum Hooks?
Hooks wurden in React für einfache Anwendungsfälle von Funktionskomponenten eingeführt, die einen Status erforderten. In vielen einfachen Fällen sind Funktionskomponenten mit Hooks viel einfacher und lesbarer als ihre entsprechenden Klassenkomponenten. Wenn jedoch asynchrone Statusaktualisierungen eingeführt und Daten zwischen Hooks oder Effekten übergeben werden, reicht das Hooks-Muster in der Regel nicht aus und eine klassenbasierte Lösung wie reaktive Controller ist besser geeignet.
Hooks und Controller für API-Anfragen
Es ist üblich, einen Hook zu schreiben, der Daten von einer API anfordert. Diese React-Funktionskomponente führt beispielsweise Folgendes aus:
index.tsx- Text wird gerendert
- Rendert die Antwort von
useAPI- Nutzer-ID + Nutzername
- Fehlermeldung
- 404, wenn Nutzer 11 erreicht wird (designbedingt)
- Fehler abbrechen, wenn der API-Abruf abgebrochen wird
- Benachrichtigung zum Laden
- Rendert eine Aktionsschaltfläche
- „Nächster Nutzer“: Ruft die API für den nächsten Nutzer ab.
- Abbrechen: Der API-Abruf wird abgebrochen und ein Fehler wird angezeigt.
useApi.tsx- Definiert einen benutzerdefinierten
useApi-Hook. - Ruft ein Nutzerobjekt asynchron von einer API ab.
- Ausgabe:
- Nutzername
- Gibt an, ob der Abruf geladen wird
- Alle Fehlermeldungen
- Ein Callback zum Abbrechen des Abrufs
- Bricht laufende Abrufe ab, wenn die Komponente demontiert wird.
- Definiert einen benutzerdefinierten
Hier finden Sie die Implementierung des Lit + Reactive-Controllers.
Fazit:
- Reaktive Controller ähneln am ehesten benutzerdefinierten Hooks.
- Nicht renderbare Daten zwischen Callbacks und Effekten übergeben
- In React wird
useRefverwendet, um Daten zwischenuseEffectunduseCallbackzu übergeben. - Lit verwendet eine private Klasseneigenschaft
- React ahmt im Wesentlichen das Verhalten einer privaten Klasseneigenschaft nach.
- In React wird
Wenn Ihnen die React-Funktionskomponentensyntax mit Hooks, aber die gleiche buildlose Umgebung von Lit gefällt, empfiehlt das Lit-Team die Haunted-Bibliothek.
Kinder
Standardslot
Wenn HTML-Elementen kein slot-Attribut zugewiesen wird, werden sie dem standardmäßigen unbenannten Slot zugewiesen. Im folgenden Beispiel wird mit MyApp ein Absatz in einen benannten Slot eingefügt. Der andere Absatz wird standardmäßig im unbenannten Slot platziert.“
@customElement("my-element")
export class MyElement extends LitElement {
render() {
return html`
<section>
<div>
<slot></slot>
</div>
<div>
<slot name="custom-slot"></slot>
</div>
</section>
`;
}
}
@customElement("my-app")
export class MyApp extends LitElement {
render() {
return html`
<my-element>
<p slot="custom-slot">
This paragraph will be placed in the custom-slot!
</p>
<p>
This paragraph will be placed in the unnamed default slot!
</p>
</my-element>
`;
}
}
Slot-Updates
Wenn sich die Struktur der untergeordneten Slots ändert, wird ein slotchange-Ereignis ausgelöst. Eine Lit-Komponente kann einen Event-Listener an ein slotchange-Ereignis binden. Im Beispiel unten werden die assignedNodes des ersten Slots, der in shadowRoot gefunden wird, am slotchange in der Konsole protokolliert.
@customElement("my-element")
export class MyElement extends LitElement {
onSlotChange(e: Event) {
const slot = this.shadowRoot.querySelector('slot');
console.log(slot.assignedNodes({flatten: true}));
}
render() {
return html`
<section>
<div>
<slot @slotchange="{this.onSlotChange}"></slot>
</div>
</section>
`;
}
}
Refs
Referenzgenerierung
Sowohl Lit als auch React stellen nach dem Aufrufen ihrer render-Funktionen eine Referenz auf ein HTMLElement bereit. Es ist jedoch sinnvoll, sich anzusehen, wie React und Lit das DOM zusammensetzen, das später über einen Lit-@query-Decorator oder eine React-Referenz zurückgegeben wird.
React ist eine funktionale Pipeline, mit der React-Komponenten und keine HTMLElements erstellt werden. Da eine Ref deklariert wird, bevor ein HTMLElement gerendert wird, wird ein Speicherplatz zugewiesen. Daher sehen Sie null als Anfangswert einer Ref, da das tatsächliche DOM-Element noch nicht erstellt (oder gerendert) wurde, d.h. useRef(null).
Nachdem ReactDOM eine React-Komponente in ein HTMLElement konvertiert hat, wird in der React-Komponente nach einem Attribut namens ref gesucht. Falls verfügbar, platziert ReactDOM die Referenz des HTMLElement auf ref.current.
LitElement verwendet die html-Vorlagen-Tag-Funktion aus lit-html, um im Hintergrund ein Template-Element zu erstellen. LitElement stempelt den Inhalt der Vorlage nach dem Rendern in das Shadow DOM eines benutzerdefinierten Elements. Das Shadow-DOM ist ein DOM-Baum mit Bereich, der von einem Shadow-Root gekapselt wird. Der Decorator @query erstellt dann eine Getter-Methode für die Eigenschaft, die im Wesentlichen ein this.shadowRoot.querySelector für den Bereichs-Root ausführt.
Mehrere Elemente abfragen
Im folgenden Beispiel gibt der Dekorator @queryAll die beiden Absätze im Shadow Root als NodeList zurück.
@customElement("my-element")
export class MyElement extends LitElement {
@queryAll('p')
paragraphs!: NodeList;
render() {
return html`
<p>Hello, world!</p>
<p>How are you?</p>
`;
}
}
Im Grunde erstellt @queryAll einen Getter für paragraphs, der die Ergebnisse von this.shadowRoot.querySelectorAll() zurückgibt. In JavaScript kann ein Getter deklariert werden, um denselben Zweck zu erfüllen:
get paragraphs() {
return this.renderRoot.querySelectorAll('p');
}
Elemente für Abfrageänderungen
Der @queryAsync-Decorator eignet sich besser für die Verarbeitung eines Knotens, der sich je nach Status einer anderen Element-Property ändern kann.
Im folgenden Beispiel wird mit @queryAsync das erste Absatz-Element gefunden. Ein Absatz-Element wird jedoch nur gerendert, wenn renderParagraph zufällig eine ungerade Zahl generiert. Die @queryAsync-Anweisung gibt ein Promise zurück, das aufgelöst wird, wenn der erste Absatz verfügbar ist.
@customElement("my-dissappearing-paragraph")
export class MyDisapppearingParagraph extends LitElement {
@queryAsync('p')
paragraph!: Promise<HTMLElement>;
renderParagraph() {
const randomNumber = Math.floor(Math.random() * 10)
if (randomNumber % 2 === 0) {
return "";
}
return html`<p>This checkbox is checked!`
}
render() {
return html`
${this.renderParagraph()}
`;
}
}
Vermittlungsstatus
In React werden üblicherweise Callbacks verwendet, da der Status von React selbst verwaltet wird. React versucht, nicht auf den von Elementen bereitgestellten Status angewiesen zu sein. Das DOM ist einfach ein Ergebnis des Rendering-Prozesses.
Externer Status
Sie können Redux, MobX oder eine andere Bibliothek zur Statusverwaltung zusammen mit Lit verwenden.
Lit-Komponenten werden im Browserbereich erstellt. Daher ist jede Bibliothek, die auch im Browserbereich vorhanden ist, für Lit verfügbar. Es wurden viele tolle Bibliotheken entwickelt, um vorhandene Statusverwaltungssysteme in Lit zu nutzen.
Hier finden Sie eine Serie von Vaadin, in der erklärt wird, wie Sie Redux in einer Lit-Komponente nutzen können.
Hier finden Sie ein Beispiel von Adobe, wie MobX in Lit für eine große Website eingesetzt werden kann.
Unter Apollo Elements erfahren Sie, wie Entwickler GraphQL in ihre Webkomponenten einbinden.
Lit funktioniert mit nativen Browserfunktionen und die meisten Lösungen zur Statusverwaltung im Browserbereich können in einer Lit-Komponente verwendet werden.
Stile
Shadow DOM
Um Stile und DOM nativ in einem benutzerdefinierten Element zu kapseln, verwendet Lit Shadow DOM. Shadow Roots generieren einen Shadow-Baum, der vom Hauptdokumentbaum getrennt ist. Das bedeutet, dass die meisten Stile auf dieses Dokument beschränkt sind. Bestimmte Stile wie Farbe und andere schriftbezogene Stile werden übernommen.
Mit Shadow DOM werden auch neue Konzepte und Selektoren in die CSS-Spezifikation eingeführt:
:host, :host(:hover), :host([hover]) {
/* Styles the element in which the shadow root is attached to */
}
slot[name="title"]::slotted(*), slot::slotted(:hover), slot::slotted([hover]) {
/*
* Styles the elements projected into a slot element. NOTE: the spec only allows
* styling the direcly slotted elements. Children of those elements are not stylable.
*/
}
Stile teilen
Mit Lit können Sie Stile ganz einfach in Form von CSSTemplateResults über css-Vorlagen-Tags zwischen Komponenten teilen. Beispiel:
// typography.ts
export const body1 = css`
.body1 {
...
}
`;
// my-el.ts
import {body1} from './typography.ts';
@customElement('my-el')
class MyEl Extends {
static get styles = [
body1,
css`/* local styles come after so they will override bod1 */`
]
render() {
return html`<div class="body1">...</div>`
}
}
Designs
Shadow-Roots stellen eine Herausforderung für das herkömmliche Theming dar, das in der Regel auf Top-down-Style-Tags basiert. Die herkömmliche Methode zum Anwenden von Designs auf Webkomponenten, die Shadow DOM verwenden, besteht darin, eine Stil-API über benutzerdefinierte CSS-Eigenschaften bereitzustellen. Hier ist ein Beispiel für ein Muster, das in Material Design verwendet wird:
.mdc-textfield-outline {
border-color: var(--mdc-theme-primary, /* default value */ #...);
}
.mdc-textfield--input {
caret-color: var(--mdc-theme-primary, #...);
}
Der Nutzer würde dann das Design der Website ändern, indem er benutzerdefinierte Eigenschaftswerte anwendet:
html {
--mdc-theme-primary: #F00;
}
html[dark] {
--mdc-theme-primary: #F88;
}
Wenn die Top-down-Gestaltung ein Muss ist und Sie keine Stile verfügbar machen können, ist es immer möglich, Shadow DOM zu deaktivieren. Dazu müssen Sie createRenderRoot überschreiben, damit this zurückgegeben wird. Das Vorlage-Rendering Ihrer Komponenten erfolgt dann für das benutzerdefinierte Element selbst und nicht für einen Shadow-Root, der an das benutzerdefinierte Element angehängt ist. Dadurch gehen die Stil- und DOM-Kapselung sowie Slots verloren.
Produktion
IE 11
Wenn Sie ältere Browser wie IE 11 unterstützen müssen, müssen Sie einige Polyfills laden, die etwa 33 KB zusätzlich ausmachen. Weitere Informationen
Bedingte Sets
Das Lit-Team empfiehlt, zwei verschiedene Bundles bereitzustellen: eines für IE 11 und eines für moderne Browser. Das hat mehrere Vorteile:
- ES 6 ist schneller und wird von den meisten Ihrer Clients unterstützt.
- Durch das Transpilieren von ES5 wird die Bundle-Größe erheblich erhöht.
- Mit bedingten Paketen erhalten Sie das Beste aus beiden Welten
- IE 11-Unterstützung
- Keine Verlangsamung bei modernen Browsern
Weitere Informationen zum Erstellen eines bedingt bereitgestellten Bundles finden Sie in unserer Dokumentation.