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 kannst du gemeinsam nutzbare Komponenten, Anwendungen, Designsysteme und vieles mehr erstellen.
Lerninhalte
So werden mehrere React-Konzepte in Lit übersetzt, z. B.:
- JSX und Vorlagen
- Komponenten und Props
- Status und Lebenszyklus
- Hooks
- Children
- Verweise
- Vermittlungsstatus
Aufgaben
Am Ende dieses Codelabs wirst du in der Lage sein, Konzepte der React-Komponente in Lit-Analoge umzuwandeln.
Voraussetzungen
- Die neueste Version von Chrome, Safari, Firefox oder Edge
- Kenntnisse in HTML, CSS, JavaScript und den Chrome-Entwicklertools
- Wissen über React
- (Fortgeschritten) Für die bestmögliche Entwicklung laden Sie VS Code herunter. Außerdem benötigen Sie das lit-Plug-in für VS Code und NPM.
2. Lit vs. React
Die Hauptkonzepte und -funktionen von Lit ähneln in vielerlei Hinsicht denen von React. Lit hat aber auch einige wichtige Unterschiede und Alleinstellungsmerkmale:
Es ist klein
Im Vergleich zu React + ReactDOM, das über 40 KB verfügt, sind nur etwa 5 KB verkleinert und mit gzip komprimiert.
Es ist schnell
In öffentlichen Benchmarks, in denen das Vorlagensystem von Lit, Lit-HTML, mit dem VDOM von React verglichen wird, ist Lit HTML im schlimmsten Fall 8–10% schneller als bei React und in den üblicheren Anwendungsfällen um 50%schneller.
LitElement (die Komponentenbasisklasse von Lit) erzeugt nur minimalen Mehraufwand für Lit-HTML, übertrifft jedoch die Leistung von React um 16–30%, wenn Komponentenfunktionen wie Arbeitsspeichernutzung, Interaktion und Startzeiten verglichen werden.
Erfordert keinen Build
Dank neuer Browserfunktionen wie ES-Modulen und getaggten Vorlagenliteralen erfordert Lit keine Kompilierung, 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 vielleicht nicht einmal NPM, um loszulegen.
Wenn Sie möchten, können Sie Lit-Code jedoch weiterhin erstellen und optimieren. Die jüngste Entwicklerkonzentrierung auf native ES-Module war gut für Lit: Lit ist einfach normales JavaScript und es sind keine frameworkspezifischen Befehlszeilen oder Build-Verarbeitungen erforderlich.
Unabhängig vom Framework
Die Komponenten von Lit basieren auf einer Reihe von Webstandards, den sogenannten Webkomponenten. Das bedeutet, dass Komponenten, die in Lit erstellt wurden, in aktuellen und zukünftigen Frameworks funktionieren. Wenn HTML-Elemente unterstützt werden, werden auch Webkomponenten unterstützt.
Die einzigen Probleme bei der Interoperabilität von Frameworks treten auf, wenn die Frameworks eine eingeschränkte Unterstützung für das DOM haben. React ist eines dieser Konzepte, aber es ermöglicht Ausstiegschancen über Refs, und Refs in React sind keine gute Entwicklererfahrung.
Das Lit-Team hat an einem experimentellen Projekt mit dem Namen @lit-labs/react
gearbeitet, das Ihre Lit-Komponenten automatisch parst und einen React-Wrapper generiert, damit Sie keine Referenzen verwenden müssen.
Außerdem erfahren Sie auf 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, Lit ist jedoch in TypeScript geschrieben und das Lit-Team empfiehlt Entwicklern, ebenfalls TypeScript zu verwenden.
Das Lit-Team arbeitet mit der Lit-Community zusammen, um Projekte zu pflegen, bei denen TypeScript-Typprüfung und -Intelligenz in Lit-Vorlagen sowohl bei der Entwicklung als auch bei der Erstellung mit lit-analyzer
und lit-plugin
zur Verfügung stehen.
Entwicklertools sind in den Browser integriert
Lit-Komponenten sind einfach HTML-Elemente im DOM. Das bedeutet, dass Sie zum Prüfen Ihrer Komponenten keine Tools oder Erweiterungen für Ihren Browser installieren müssen.
Öffnen Sie einfach die Entwicklertools, wählen Sie ein Element aus und sehen Sie sich seine Eigenschaften oder seinen Status an.
Bei der Entwicklung wurde das serverseitige Rendering (SSR) berücksichtigt.
Bei der Entwicklung von Lit 2 wurde auf die Unterstützung von SSR geachtet. Zum Zeitpunkt der Erstellung dieses Codelabs musste das Lit-Team die SSR-Tools noch nicht in einer stabilen Form veröffentlichen. Das Lit-Team hat jedoch bereits serverseitig gerenderte Komponenten für Google-Produkte bereitgestellt und die SSR in React-Anwendungen getestet. Das Lit-Team wird diese Tools voraussichtlich bald extern auf GitHub veröffentlichen.
In der Zwischenzeit kannst du den Fortschritt des Lit-Teams hier verfolgen.
Es ist ein geringer Einstieg
Die Nutzung von Lit ist nicht mit großen Verpflichtungen verbunden. Sie können Komponenten in Lit erstellen und Ihrem vorhandenen Projekt hinzufügen. Wenn Ihnen die Webkomponenten nicht gefallen, müssen Sie nicht die gesamte App auf einmal konvertieren, da sie auch in anderen Frameworks funktionieren.
Hast du eine ganze App in Lit erstellt und möchtest du sie ändern? In diesem Fall können Sie Ihre aktuelle Lit-Anwendung in Ihr neues Framework einfügen und alles, was Sie möchten, in die Komponenten des neuen Frameworks migrieren.
Außerdem unterstützen viele moderne Frameworks die Ausgabe in Webkomponenten. Das bedeutet, dass sie in der Regel in ein Lit-Element passen.
3. Einrichten und Playground kennenlernen
Für dieses Codelab gibt es zwei Möglichkeiten:
- Sie können das ganz einfach online im Browser tun.
- (Fortgeschritten) Mit VS Code auf dem lokalen Computer
Auf den Code zugreifen
Im Codelab finden Sie Links zum Lit-Playground, z. B. diesen:
Das Playground ist eine Code-Sandbox, die vollständig in Ihrem Browser ausgeführt wird. Sie kann TypeScript- und JavaScript-Dateien kompilieren und ausführen und auch Importe in Knotenmodule automatisch auflösen.
// 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 ausführen und diese Prüfpunkte als Ausgangspunkt verwenden. Wenn Sie VS Code verwenden, können Sie mithilfe dieser Checkpoints den Startcode für jeden Schritt herunterladen und Ihre Arbeit überprüfen.
Erkundung der beleuchteten Spielplatz-Benutzeroberfläche
Im Screenshot der Benutzeroberfläche von Lit Playground sind die Abschnitte hervorgehoben, die Sie in diesem Codelab verwenden werden.
- Dateiauswahl Beachten Sie das Pluszeichen...
- Dateieditor
- Codevorschau
- Schaltfläche „Aktualisieren“.
- Grafik: Symbol zum Herunterladen
VS Code-Einrichtung (erweitert)
Diese VS Code-Einrichtung bietet folgende Vorteile:
- Prüfung des Vorlagentyps
- Vorlagen-Intelligence und automatische Vervollständigung
Wenn Sie NPM und VS Code (mit dem lit-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:
- Klicken Sie auf die Schaltfläche zum Herunterladen.
- Inhalt der TAR-Datei in ein Verzeichnis extrahieren
- (Bei TS) Quick tsconfig einrichten, die ES-Module und ES2015+ ausgibt
- Installieren Sie einen dev-Server, der Bare-Modul-Bezeichner auflösen kann. Das Lit-Team empfiehlt @web/dev-server.
- Hier ein Beispiel:
package.json
- Hier ein Beispiel:
- 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 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 ihrem JavaScript-Code schreiben können. Lit-Vorlagen dienen einem ähnlichen Zweck: Sie stellen die Benutzeroberfläche einer Komponente als Funktion ihres Status dar.
Grundlegende Syntax
In React würden Sie eine JSX-Hello World wie folgt rendern:
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 enthaltene „name“-Variable. In Lit würden Sie Folgendes tun:
import {html, render} from 'lit';
const name = 'Josh Perez';
const element = html`
<h1>Hello, ${name}</h1>
<div>How are you?</div>`;
render(
element,
mountNode
);
Für Lit-Vorlagen ist kein React-Fragment erforderlich, um mehrere Elemente in den Vorlagen zu gruppieren.
In „Lit“ sind Vorlagen mit html
getaggten LIT-Vorlagen umschlossen. Lit erhält ebenfalls ihren Namen.
Vorlagenwerte
Lit-Vorlagen können andere Lit-Vorlagen enthalten, die als TemplateResult
bezeichnet werden. Umschließen Sie name
beispielsweise in Kursivschrift-Tags (<i>
) und in einem getaggten Vorlagenliteral. Hinweis: Verwenden Sie das Graviszeichen (`
) und nicht das Apostroph ('
).
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
);
Lit-TemplateResult
s können Arrays, Strings, andere TemplateResult
s und Anweisungen enthalten.
Als Übung können Sie versuchen, 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
);
Passieren und Requisiten aufstellen
Einer der größten Unterschiede zwischen der JSX- und der Lit-Syntax ist die Syntax der Datenbindung. Sehen wir uns beispielsweise diese React-Eingabe mit Bindungen an:
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 ausführt:
- Legt „disabled“ auf eine definierte Variable fest (in diesem Fall „false“)
- Legt die Klasse auf
static-class
plus eine Variable fest (in diesem Fall"static-class my-class"
) - Legt einen Standardwert fest
In Lit würden Sie Folgendes tun:
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 disabled
-Attribut umzuschalten.
Als Nächstes erfolgt eine Bindung direkt an das class
-Attribut und nicht an className
. Sie können dem Attribut class
mehrere Bindungen hinzufügen, es sei denn, Sie verwenden die Anweisung classMap
, die ein deklaratives Hilfsprogramm zum Wechseln von Klassen ist.
Schließlich wird die Eigenschaft value
auf die Eingabe festgelegt. Anders als in React wird das Eingabeelement dadurch nicht als schreibgeschützt festgelegt, da es der nativen Implementierung und dem Verhalten der Eingabe folgt.
Syntax für die Bindung von Lit-Properties
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
disabled
alsdisabled="false"
verwenden, vom DOM trotzdem als „true“ interpretiert werden, weilinputElement.hasAttribute('disabled') === true
html`<my-element .property-name=${anyVar}>`;
- Das Präfix
.
ist die Bindungssyntax zum Festlegen einer Eigenschaft eines Elements. - Entspricht
inputRef.propertyName = anyVar
- Gut geeignet für die Weitergabe 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)
- Geeignet für Basiswerte, Stilregelselektoren 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“ wird protokolliert, 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 Ereignis click
ein Listener mit @click
hinzugefügt.
Als Nächstes wird nicht onChange
verwendet, sondern eine Bindung an das native input
-Ereignis von <input>
, da das native change
-Ereignis nur bei blur
ausgelöst wird (Reaktion-Zusammenfassungen über diese Ereignisse).
Syntax für Lit-Event-Handler
html`<my-element @event-name=${() => {...}}></my-element>`;
- Das Präfix
@
ist die Bindungssyntax für einen Ereignis-Listener. - Entspricht
inputRef.addEventListener('event-name', ...)
- Verwendet native DOM-Ereignisnamen
5. Komponenten &Requisiten
In diesem Abschnitt erfahren Sie mehr über die Komponenten und Funktionen der Lit-Klasse. Status und Hooks werden in späteren Abschnitten ausführlicher behandelt.
Klassenkomponenten und LitElement
Das Lit-Äquivalent einer React-Klassenkomponente ist LitElement und das Konzept der "reaktiven Eigenschaften" von Lit setzt sich aus den Eigenschaften und dem Status von React zusammen. 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 eine
name
- Legt den Standardwert von
name
auf einen leeren String fest (""
) name
wird"Elliott"
neu zugewiesen
So würden Sie dies 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>
Zusammenfassung des Vorgangs im obigen Beispiel:
@property({type: String})
name = '';
- Definiert eine öffentliche reaktive Eigenschaft, also einen Teil der öffentlichen API Ihrer Komponente
- Stellt (standardmäßig) ein Attribut sowie eine Property für Ihre Komponente bereit
- Hier wird definiert, wie das Attribut der Komponente (Strings) in einen Wert umgewandelt wird.
static get properties() {
return {
name: {type: String}
}
}
- Er hat dieselbe Funktion wie der TS-Decorator
@property
, 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 Property ändert.
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
...
}
- Hiermit wird der Tag-Name eines HTML-Elements einer Klassendefinition zugeordnet.
- Gemäß dem Standard für benutzerdefinierte Elemente muss der Tag-Name einen Bindestrich (-) enthalten.
this
in einem LitElement verweist auf die Instanz des benutzerdefinierten Elements (in diesem Fall<welcome-banner>
).
customElements.define('welcome-banner', WelcomeBanner);
- Dies ist das JavaScript-Äquivalent des
@customElement
-TS-Decorators.
<head>
<script type="module" src="./index.js"></script>
</head>
- Importiert die Definition des benutzerdefinierten Elements
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
- Das benutzerdefinierte Element wird der Seite hinzugefügt.
- Legt das Attribut
name
auf'Elliott'
fest
Funktionskomponenten
In Lit gibt es keine 1:1-Interpretation einer Funktionskomponente, da sie weder JSX noch einen Präprozessor verwendet. Es ist jedoch recht einfach, eine Funktion zu erstellen, die Eigenschaften annimmt und DOM basierend auf diesen Eigenschaften rendert. Beispiel:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Elliott"/>
ReactDOM.render(
element,
mountNode
);
In Lit würde das so aussehen:
import {html, render} from 'lit';
function Welcome(props) {
return html`<h1>Hello, ${props.name}</h1>`;
}
render(
Welcome({name: 'Elliott'}),
document.body.querySelector('#root')
);
6. Status und Lebenszyklus
In diesem Abschnitt erfahren Sie mehr über den Status und Lebenszyklus von Lit.
Status
Das Konzept „Reactive Properties“ von Lit ist eine Mischung aus den State- und Props-Objekten von React. Wenn sich reaktive Eigenschaften ändern, kann der Komponentenlebenszyklus ausgelöst werden. Es gibt zwei Varianten von reaktiven Properties:
Öffentliche reaktive Attribute
// 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 von
@property
- Ähnlich wie Props und State in React, aber veränderlich
- Öffentliche API, auf die Nutzer der Komponente zugreifen und sie festlegen können
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 von
@state
- Ähnlich wie der Status von React, aber veränderlich
- Privater interner Status, auf den in der Regel innerhalb der Komponente oder Unterklassen zugegriffen wird
Lifecycle
Der Lebenszyklus von Lit ähnelt dem von React, es gibt jedoch einige bemerkenswerte 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';
}
}
- Das entspricht auch
constructor
- Es ist nicht erforderlich, dem Super-Aufruf etwas zu übergeben.
- Aufgerufen durch (nicht vollständig inklusiv):
document.createElement
document.innerHTML
new ComponentClass()
- Auf der Seite befindet sich ein nicht aktualisierter Tag-Name und die Definition wird geladen und mit
@customElement
odercustomElements.define
registriert.
- Ähnlich wie
constructor
von React
render
// React
render() {
return <div>Hello World</div>
}
// Lit
render() {
return html`<div>Hello World</div>`;
}
- Das entspricht auch
render
- Kann jedes renderbare Ergebnis zurückgeben, z.B.
TemplateResult
oderstring
usw. - Ähnlich wie „React“ sollte
render()
eine reine Funktion sein - Wird in jedem Knoten gerendert, den
createRenderRoot()
zurückgibt (standardmäßigShadowRoot
)
componentDidMount
componentDidMount
ähnelt einer Kombination aus den firstUpdated
- und connectedCallback
-Lebenszyklus-Callbacks von Lit.
firstUpdated
import Chart from 'chart.js';
// React
componentDidMount() {
this._chart = new Chart(this.chartElRef.current, {...});
}
// Lit
firstUpdated() {
this._chart = new Chart(this.chartEl, {...});
}
- Wird zum ersten Mal aufgerufen, wenn die Vorlage der Komponente in der Wurzel der Komponente gerendert wird.
- Wird nur aufgerufen, wenn das Element verbunden ist. Wird also nicht über
document.createElement('my-component')
aufgerufen, bis dieser Knoten in den DOM-Baum eingefügt wurde. - Hier können Sie die Komponenten so einrichten, dass das von der Komponente gerenderte DOM erforderlich ist.
- Im Gegensatz zu Reacts
componentDidMount
führen Änderungen an reaktiven Eigenschaften infirstUpdated
zu einem erneuten Rendern. Der Browser führt die Änderungen jedoch in der Regel im selben Frame aus. Wenn für diese Änderungen kein Zugriff auf das DOM der Wurzel erforderlich ist, sollten sie normalerweise inwillUpdate
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 die DOM-Baumstruktur eingefügt wird
- Wenn benutzerdefinierte Elemente vom DOM getrennt werden, werden sie im Gegensatz zu React-Komponenten nicht gelöscht und können daher mehrmals „verbunden“ werden.
firstUpdated
wird nicht noch einmal aufgerufen
- Nützlich, um das DOM neu zu initialisieren oder Ereignis-Listener, die beim Trennen der Verbindung bereinigt wurden, wieder hinzuzufügen
- Hinweis:
connectedCallback
wird möglicherweise vorfirstUpdated
aufgerufen. Daher ist das DOM beim ersten Aufruf 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);
}
}
- Das Lit-Äquivalent ist
updated
(die englische Vergangenheitsform von „update“). - Im Gegensatz zu React wird
updated
auch beim ersten Rendern aufgerufen. - Funktioniert ähnlich wie
componentDidUpdate
in React
componentWillUnmount
// React
componentWillUnmount() {
this.window.removeEventListener('resize', this.boundOnResize);
}
// Lit
disconnectedCallback() {
super.disconnectedCallback();
this.window.removeEventListener('resize', this.boundOnResize);
}
- Liquiditätsäquivalent entspricht
disconnectedCallback
- Im Gegensatz zu React-Komponenten wird die Komponente nicht zerstört, wenn benutzerdefinierte Elemente vom DOM getrennt werden.
- Im Gegensatz zu
componentWillUnmount
wirddisconnectedCallback
nach dem Entfernen des Elements aus dem Baum aufgerufen. - DOM innerhalb des Stamms ist noch an die Unterstruktur des Stamms angehängt
- Nützlich zum Bereinigen von Event-Listenern und Leaky-Referenzen, damit der Browser die Komponente per Speicherbereinigung bereinigen kann
Übung
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:
- Sie rendert „Hello World! Es ist“ und dann die Uhrzeit.
- Jede Sekunde wird die Uhr aktualisiert
- Wenn der Sensor entfernt wird, wird das Intervall gelöscht, das die Markierung auslöst.
Beginnen Sie zunächst 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);
Initialisieren Sie als Nächstes date
und deklarieren Sie es als interne reaktive Eigenschaft mit @state
, 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 Tick-Methode.
tick() {
this.date = new Date();
}
Als Nächstes kommt die Implementierung von componentDidMount
. Auch hier ist das Lit-Analog eine Mischung aus firstUpdated
und connectedCallback
. Im Fall dieser Komponente benötigt der Aufruf von tick
mit setInterval
keinen Zugriff auf das DOM im Stammverzeichnis. Außerdem wird das Intervall gelöscht, wenn das Element aus dem Dokumentenbaum entfernt wird. Wenn es wieder angehängt wird, muss das Intervall also neu gestartet werden. Daher ist connectedCallback
hier 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 abschließend das Intervall, damit der Tick nicht ausgeführt wird, nachdem das Element vom Dokumentenbaum getrennt wurde.
// Lit (TS & JS)
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
Das Ganze sollte wie folgt aussehen:
// 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. Hooks
In diesem Abschnitt erfährst du, wie du React Hook-Konzepte in Lit
Die Konzepte von React-Hooks
React-Hooks bieten eine Möglichkeit, Funktionskomponenten in einen Status zu "verknüpfen". Dies hat mehrere Vorteile.
- Sie vereinfachen die Wiederverwendung zustandsbasierter Logik.
- Hilfe beim Aufteilen einer Komponente in kleinere Funktionen
Darüber hinaus wurden mit dem Fokus auf funktionsbasierten Komponenten bestimmte Probleme mit der klassenbasierten Syntax von React behoben, z. B.:
props
muss vonconstructor
nachsuper
übergeben werden- Die unordentliche Initialisierung von Properties in der
constructor
- Dies war ein Grund, der damals vom React-Team angegeben wurde, aber mit ES2019 behoben wurde.
- Probleme, die durch
this
verursacht werden, die sich nicht mehr auf die Komponente beziehen
React-Hooks-Konzepte in Lit
Wie im Abschnitt Komponenten und Requisiten erwähnt, bietet Lit keine Möglichkeit, benutzerdefinierte Elemente aus einer Funktion zu erstellen. LitElement löst jedoch die meisten Hauptprobleme mit Komponenten der React-Klasse. 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?
constructor
nimmt keine Argumente an.- Alle
@event
Bindungen automatisch anthis
binden this
bezieht sich in den meisten Fällen auf den Verweis des benutzerdefinierten Elements- Klassenattribute können jetzt als Klassenmitglieder instanziiert werden. Dadurch werden konstruktorbasierte Implementierungen bereinigt.
Reaktive Controller
Die Hauptkonzepte hinter Hooks werden in Lit als reaktive Controller bezeichnet. Mit reaktiven Controller-Mustern können Sie zustandsabhängige Logik teilen, Komponenten in kleinere, modularere Teile aufteilen und sich an den Aktualisierungslebenszyklus eines Elements anschließen.
Ein reaktiver Controller ist eine Objektschnittstelle, die in den Aktualisierungszyklus eines Controller-Hosts wie LitElement eingebunden werden kann.
Der Lebenszyklus von ReactiveController
und 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 mit addController
an einen Host anhängen, wird der Lebenszyklus des Controllers zusammen mit dem des Hosts aufgerufen. Denken Sie zum Beispiel an die Uhr 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 „Hallo Welt! Es ist“ und dann die Uhrzeit.
- Jede Sekunde wird die Uhr aktualisiert
- Wenn der Sensor entfernt wird, wird das Intervall gelöscht, das die Markierung auslöst.
Bau des Gerüsts der Komponente
Beginnen Sie zuerst 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
, erstellen Sie einen Kurs 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 beliebig erstellt werden, solange er die ReactiveController
-Schnittstelle verwendet. Die Verwendung einer Klasse mit einer constructor
, die eine ReactiveControllerHost
-Schnittstelle sowie alle anderen Eigenschaften annehmen kann, die zum Initialisieren des Controllers erforderlich sind, ist ein Muster, das das Lit-Team in den meisten einfachen Fällen bevorzugt.
Jetzt müssen Sie die React-Lebenszyklusereignisse in Controller-Ereignisse umwandeln. Kurzum:
componentDidMount
- Zu
connectedCallback
von LitElement - Zum
hostConnected
des Controllers
- Zu
ComponentWillUnmount
- In
disconnectedCallback
von LitElement - Zum
hostDisconnected
des Controllers
- In
Weitere Informationen zur Umwandlung des React-Lebenszyklus in den Lit-Lebenszyklus finden Sie im Abschnitt Status und Lebenszyklus.
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.
// 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 Uhrencontroller verwenden möchten, importieren Sie ihn 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);
Wenn Sie den Controller verwenden möchten, müssen Sie ihn durch Weitergabe einer Referenz auf den Controller-Host (die <my-element>
-Komponente) instanziieren und dann in der render
-Methode verwenden.
Erneute Renderings im Controller auslösen
Die Uhrzeit wird angezeigt, wird 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 die date
in der ClockController
-Klasse ändert und nicht mehr in der Komponente. Das bedeutet, dass der Host angewiesen werden muss, seinen Update-Lebenszyklus mit host.requestUpdate()
auszuführen, nachdem date
auf dem Controller festgelegt wurde.
// Lit (TS & JS) - clock.ts / clock.js
private tick() {
this.date = new Date();
this.host.requestUpdate();
}
Jetzt sollte die Uhr ticken!
Einen detaillierteren Vergleich häufiger Anwendungsfälle mit Hooks finden Sie im Abschnitt Erweiterte Themen – Aufhänger.
8. Children
In diesem Abschnitt erfahren Sie, wie Sie mithilfe von Slots untergeordnete Elemente in Lit.
Spielautomaten und Kinder
Slots ermöglichen die Komposition, da Sie damit Komponenten verschachteln können.
In React werden untergeordnete Elemente durch Requisiten vererbt. Der Standard-Slot ist props.children
und die Funktion render
definiert, wo der Standard-Slot platziert wird. Beispiel:
const MyArticle = (props) => {
return <article>{props.children}</article>;
};
props.children
sind React-Komponenten und keine HTML-Elemente.
In „Lit“ bestehen untergeordnete Elemente in der Renderingfunktion mit Slot-Elementen. Beachten Sie, dass untergeordnete Elemente nicht auf dieselbe Weise übernommen werden wie React. In Lit sind untergeordnete Elemente HTMLElements, die an Slots angehängt sind. Dieser Anhang wird als Projektion bezeichnet.
@customElement("my-article")
export class MyArticle extends LitElement {
render() {
return html`
<article>
<slot></slot>
</article>
`;
}
}
Mehrere Slots
In React entspricht das Hinzufügen mehrerer Anzeigenflächen im Grunde der Übernahme weiterer Eigenschaften.
const MyArticle = (props) => {
return (
<article>
<header>
{props.headerChildren}
</header>
<section>
{props.sectionChildren}
</section>
</article>
);
};
Wenn du mehr <slot>
-Elemente hinzufügst, werden auch mehr Slots in Lit erstellt. Mehrere Slots sind mit dem Attribut name
definiert: <slot name="slot-name">
. So können Kinder angeben, welcher Slot ihnen zugewiesen werden soll.
@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>
`;
}
}
Standardinhalt des Slots
Slots zeigen ihren untergeordneten Knoten an, wenn keine Knoten auf diesen Slot projiziert werden. Wenn Knoten in einen Slot projiziert werden, zeigt dieser Slot nicht seine Unterstruktur, sondern projizierte Knoten an.
@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>
`;
}
}
Untergeordnete Slots zuweisen
In React werden untergeordnete Elemente Slots über die Eigenschaften einer Komponente zugewiesen. Im folgenden Beispiel werden React-Elemente an die Props 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 Slots mithilfe des Attributs slot
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 kein Standardslot (z.B. <slot>
) und kein Slot mit einem name
-Attribut (z.B. <slot name="foo">
) vorhanden ist, das mit dem slot
-Attribut der untergeordneten Elemente des benutzerdefinierten Elements übereinstimmt (z.B. <div slot="foo">
), wird dieser Knoten nicht projiziert und nicht angezeigt.
9. Verweise
Gelegentlich müssen Entwickler auf die API eines HTMLElements zugreifen.
In diesem Abschnitt erfahren Sie, wie Sie Elementreferenzen in Lit.
Referenzen reagieren
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 HTMLElements gerendert.
In React sind Refs Speicherplatz im Arbeitsspeicher, der einen generierten 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 Schaltfläche mit Text rendern
- Den Fokus auf die Eingabe legen, wenn auf die Schaltfläche geklickt wird
Nach dem ersten Rendern setzt React inputRef.current
über das Attribut ref
auf die generierte HTMLInputElement
.
„References“ (Referenzen) mit @query
austauschen
Die Bibliothek befindet sich nah am Browser und bietet eine sehr dünne Abstraktion nativer Browserfunktionen.
Die React-Entsprechung für refs
in Lit ist das HTMLElement, das von den Decorators @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 Eigenschaft in
MyElement
mithilfe des Decorators@query
(durch Erstellen eines Getters für einenHTMLInputElement
). - Hier wird ein Rückruf für das Klickereignis namens
onButtonClick
deklariert und angehängt. - Der Fokus der Eingabe wird auf den Klick auf die Schaltfläche gelegt.
In JavaScript führen die @query
- und @queryAll
-Decorators querySelector
und querySelectorAll
aus. Das ist das JavaScript-Äquivalent zu @query('input') inputEl!: HTMLInputElement;
.
get inputEl() {
return this.renderRoot.querySelector('input');
}
Nachdem die Lit-Komponente die Vorlage der render
-Methode an das Stammelement von my-element
gesendet hat, kann inputEl
über den @query
-Dekorator jetzt das erste input
-Element zurückgeben, das im Renderstammelement gefunden wird. Wenn das angegebene Element nicht gefunden werden kann, wird null
zurückgegeben.
Wenn sich im Render-Stamm mehrere input
-Elemente befinden, gibt @queryAll
eine Liste von Knoten zurück.
10. Vermittlungsstatus
In diesem Abschnitt erfahren Sie, wie Sie den Status zwischen Komponenten in Lit vermitteln.
Wiederverwendbare Komponenten
React ahmt funktionale Rendering-Pipelines mit einem Top-down-Datenfluss nach. Eltern vermitteln Kindern durch Requisiten einen Zustand. Kinder kommunizieren über Callbacks in Props mit den Eltern.
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 auf Grundlage des Werts
props.step
. - Rendert eine Schaltfläche mit dem Label „+step“ oder „-step“
- Aktualisiert die übergeordnete Komponente, indem
props.addToCounter
mitprops.step
als Argument bei Klick aufgerufen wird
Obwohl es möglich ist, Callbacks in Lit zu übergeben, sind die herkömmlichen Muster anders. Die React-Komponente im Beispiel oben könnte im Beispiel unten als Lit-Komponente 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 Beispiel oben führt eine Lit-Komponente Folgendes aus:
- Die reaktive Property
step
erstellen - Löst ein benutzerdefiniertes Ereignis namens
update-counter
aus, das beim Klick den Wertstep
des Elements enthält.
Browserereignisse werden von untergeordneten zu übergeordneten Elementen weitergegeben. Mit Ereignissen können Kinder Interaktionsereignisse und Statusänderungen übertragen. React übergibt den Status grundsätzlich in die entgegengesetzte Richtung. Daher ist es eher unüblich, dass React-Komponenten Ereignisse auf dieselbe Weise wie Lit-Komponenten senden und empfangen.
Komponenten mit Status
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, wie 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>
);
};
Im obigen Beispiel geschieht Folgendes:
- Erstellt einen
count
-Status. - Erstellt einen Callback, der dem Status
count
eine Nummer hinzufügt. CounterButton
verwendetaddToCounter
, umcount
bei jedem Klick umstep
zu 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 für ein übergeordnetes Element 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>
`;
}
}
Im obigen Beispiel geschieht Folgendes:
- Erstellt eine reaktive Property namens
count
, die die Komponente aktualisiert, wenn sich der Wert ändert. - Bindet den
addToCounter
-Callback an den@update-counter
-Ereignis-Listener - Aktualisiert
count
, indem der indetail.step
desupdate-counter
-Ereignisses gefundene Wert hinzugefügt wird - Legt den
step
-Wert voncounter-button
über das Attributstep
fest
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 aufzurufen.
Dieser Ansatz entspricht den Best Practices und entspricht dem Ziel von Lit, plattformübergreifende Unterstützung für Webkomponenten bereitzustellen.
11. Stile
In diesem Abschnitt lernen Sie die Stile in Lit.
Stile
Lit bietet mehrere Möglichkeiten, Elemente zu stylen, sowie eine integrierte Lösung.
Inline-Stile
Lit unterstützt Inline-Styles und 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, jeweils mit einem Inline-Stil.
Importieren Sie jetzt einen Rahmen von 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 lästig sein, die Stilzeichenfolge jedes Mal berechnen zu müssen, daher bietet Lit eine entsprechende Anweisung an.
styleMap
Die directive styleMap
vereinfacht die Verwendung von JavaScript zum Festlegen von Inline-Styles. 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
h1
mit Rahmen und einer Farbauswahl an - Ändert den
border-color
in den Wert aus der Farbauswahl
Außerdem gibt es styleMap
, mit dem die Stile der h1
festgelegt werden. styleMap
folgt einer Syntax, die der style
-Attributbindungssyntax von React ähnelt.
CSSResult
Wir empfehlen, das css
-getaggte Vorlagenliteral zu verwenden, um Komponenten zu stylen.
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>
`;
}
}
Im obigen Beispiel geschieht Folgendes:
- Deklariert ein CSS-getaggtes Vorlagenliteral mit einer Bindung
- Legt die Farben von zwei
h1
s mit IDs fest
Vorteile des css
-Vorlagetags:
- Einmal pro Kurs oder pro Instanz geparst
- Modular und wiederverwendbar
- Stile lassen sich ganz einfach in eigene Dateien trennen.
- Kompatibel mit der Polyfill-Funktion für benutzerdefinierte CSS-Properties
Beachten Sie außerdem das <style>
-Tag in index.html
:
<!-- index.html -->
<style>
h1 {
color: red !important;
}
</style>
Lit ordnet die Stile Ihrer Komponenten ihren Wurzeln zu. Das bedeutet, dass Stile nicht ein- und herausragen. Zur Übergabe von Stilen an Komponenten empfiehlt das Lit-Team die Verwendung von benutzerdefinierten CSS-Eigenschaften, da diese den Gültigkeitsbereich von Lit-Stilen durchdringen können.
Stil-Tags
Sie können <style>
-Tags auch einfach inline in Ihre Vorlagen einfügen. Der Browser dedupliziert diese Stil-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 CSSResult
s im Browser viel schneller.
Link-Tags
Die Verwendung von <link rel="stylesheet">
in Ihrer Vorlage ist auch für Stile möglich. Dies wird jedoch nicht empfohlen, da dies zu einem anfänglichen Blitzen von Inhalten ohne Stil (FOUC) führen kann.
12. Erweiterte Themen (optional)
JSX und Vorlagen
Lit und Virtual DOM
Lit-HTML enthält kein konventionelles virtuelles DOM, das die einzelnen Knoten unterscheidet. Stattdessen werden Leistungsmerkmale genutzt, die der Spezifikation Tagged-Vorlagenliteral von ES2015 unverzichtbar sind. Getaggte Vorlagenliterale sind Vorlagenliteralstrings, an die Tag-Funktionen angehängt sind.
Hier ein Beispiel für ein Vorlagenliteral:
const str = 'string';
console.log(`This is a template literal ${str}`);
Hier ist ein Beispiel für ein getaggtes Vorlagenliteral:
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ßer Vorteil der Leistung in „Lit“ besteht darin, dass die an die Tag-Funktion übergebenen String-Arrays denselben Zeiger haben (siehe zweite console.log
). Der Browser erstellt nicht bei jedem Tag-Funktionsaufruf ein neues strings
-Array neu, da er dasselbe Vorlagenliteral verwendet (d.h. an derselben Stelle in der AST). So können die Bindung, das Parsen und das Vorlagen-Caching von Lit diese Funktionen ohne großen Overhead bei der Laufzeitdifferenzierung nutzen.
Dieses integrierte Browserverhalten von getaggten Vorlagenliteralen bietet Lit einen erheblichen Leistungsvorteil. Die meisten herkömmlichen virtuellen DOMs erledigen den Großteil ihrer Arbeit in JavaScript. Bei getaggten Vorlagenliteralen erfolgt der Großteil des Vergleichs jedoch im C++-Code des Browsers.
Wenn Sie HTML-getaggte Vorlagenliterale in React oder Preact verwenden möchten, empfiehlt das Lit-Team die htm
-Bibliothek.
Allerdings werden Sie, wie bei der Google Codelabs-Website und mehreren Online-Code-Editoren, feststellen, dass die literale Syntaxhervorhebung in Vorlagen nicht sehr üblich ist. Einige IDEs und Texteditoren unterstützen diese standardmäßig, z. B. Atom und der Codeblock Highlighter von GitHub. Das Lit-Team arbeitet außerdem eng mit der Community zusammen, um Projekte wie das lit-plugin
zu pflegen. Dies ist ein VS Code-Plug-in, das Ihre Lit-Projekte um Syntaxhervorhebung, Typprüfung und Intelligenz ergänzt.
Lit und JSX + React DOM
JSX wird nicht im Browser ausgeführt, sondern mit einem Preprozessor in JavaScript-Funktionsaufrufe umgewandelt (in der Regel über Babel).
Babel wandelt beispielsweise Folgendes um:
const element = <div className="title">Hello World!</div>;
ReactDOM.render(element, mountNode);
in diese Datei einfügen:
const element = React.createElement('div', {className: 'title'}, 'Hello World!');
ReactDOM.render(element, mountNode);
React DOM verwendet dann die React-Ausgabe und übersetzt sie in das tatsächliche DOM – Eigenschaften, Attribute, Ereignis-Listener und so weiter.
Lit-HTML verwendet getaggte Vorlagenliterale, die ohne Transpilation oder Präprozessor im Browser ausgeführt werden können. Um mit Lit zu beginnen, benötigen Sie also nur eine HTML-Datei, ein ES-Modulscript und einen Server. Hier ist ein vollständig im Browser ausführbares Script:
<!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 Vorlagensystem von Lit, lit-html, kein herkömmliches virtuelles DOM verwendet, sondern direkt das DOM-API nutzt, liegt die Größe von Lit 2 unter 5 KB minimiert und gzip-im Vergleich zu React (2,8 KB) und React (39,4 KB) mit 40 KB minimiert und gizip.
Ereignisse
React verwendet ein synthetisches Ereignissystem. Das bedeutet, dass die Reaktionsdomäne jedes Ereignis definieren muss, das für jede Komponente verwendet wird, und ein CamelCase-Ereignis-Listener-Äquivalent für jeden Knotentyp bereitstellen muss. Daher verfügt JSX über keine Methode zum Definieren eines Ereignis-Listeners für ein benutzerdefiniertes Ereignis. Entwickler müssen eine ref
verwenden und dann imperativ einen Listener anwenden. Dies führt zu einer suboptimalen Entwicklererfahrung bei der Integration von Bibliotheken, die React nicht berücksichtigen. Daher muss ein React-spezifischer Wrapper geschrieben werden.
Lit-html greift direkt auf das DOM zu und verwendet native Ereignisse, sodass das Hinzufügen von Ereignis-Listenern so einfach ist wie @event-name=${eventNameListener}
. Das bedeutet, dass beim Hinzufügen von Ereignis-Listenern und zum Auslösen von Ereignissen weniger das Parsen der Laufzeit durchgeführt wird.
Komponenten &Requisiten
React-Komponenten und benutzerdefinierte Elemente
Intern verwendet LitElement benutzerdefinierte Elemente, um seine Komponenten zu verpacken. Bei benutzerdefinierten Elementen gibt es einige Kompromisse zwischen React-Komponenten bei der Komponentenisierung. Status und Lebenszyklus werden im Abschnitt Status und Lebenszyklus näher erläutert.
Einige Vorteile von benutzerdefinierten Elementen als Komponentensystem:
- Sie sind browsereigen und erfordern keine Tools.
- Passend für jede Browser-API von
innerHTML
überdocument.createElement
bisquerySelector
- Können in der Regel in verschiedenen Frameworks verwendet werden
- Kann verzögert bei
customElements.define
und DOM „hydrate“ registriert werden
Einige Nachteile von benutzerdefinierten Elementen im Vergleich zu React-Komponenten:
- Ein benutzerdefiniertes Element kann nicht ohne Definition einer Klasse erstellt werden (d. h. ohne JSX-ähnliche funktionale Komponenten).
- Muss ein schließendes Tag
- enthalten.
- Hinweis: Trotz der Vorteile für Entwickler bedauern Browseranbieter 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, der zu Layoutproblemen führen kann.
- Muss über JavaScript registriert werden
Bei Lit wurden benutzerdefinierte Elemente anstelle eines benutzerdefinierten Elementsystems verwendet, da die benutzerdefinierten Elemente in den Browser eingebunden sind. Das Lit-Team ist der Meinung, dass die Vorteile eines plattformübergreifenden Frameworks die Vorteile einer Komponentenabstraktionsschicht überwiegen. Tatsächlich haben die Bemühungen des Lit-Teams im Bereich „lit-ssr“ die Hauptprobleme bei der JavaScript-Registrierung behoben. Außerdem nutzen einige Unternehmen wie GitHub die Lazy-Registrierung benutzerdefinierter Elemente, um Seiten nach und nach mit optionalen Designelementen zu optimieren.
Daten an benutzerdefinierte Elemente übergeben
Ein weitverbreiteter Irrglaube bei benutzerdefinierten Elementen ist, dass Daten nur als Strings übergeben werden können. Diese Missverständnis ergibt sich wahrscheinlich aus der Tatsache, dass Elementattribute nur als Strings geschrieben werden können. Obwohl es stimmt, dass Lit Zeichenfolgenattribute in ihre definierten Typen umwandelt, können benutzerdefinierte Elemente auch komplexe Daten als Eigenschaften akzeptieren.
Beispiel: Hier ist die Definition eines LitElements:
// 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 Eigenschaft num
definiert, die den Stringwert eines Attributs in einen number
umwandelt. Anschließend wird eine komplexe Datenstruktur mit attribute:false
eingeführt, wodurch die Attributverarbeitung von Lit deaktiviert wird.
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>
Status und Lebenszyklus
Andere React-Lebenszyklus-Callbacks
static getDerivedStateFromProps
In Lit gibt es keine entsprechende Funktion, da Props und State dieselben Klasseneigenschaften sind.
shouldComponentUpdate
- Der Lit-Wert ist
shouldUpdate
. - Wird im Gegensatz zu React beim ersten Rendering aufgerufen
- Funktioniert ähnlich wie
shouldComponentUpdate
in React
getSnapshotBeforeUpdate
In Lit ist getSnapshotBeforeUpdate
sowohl update
als auch willUpdate
ähnlich.
willUpdate
- Vor dem
update
angerufen - Im Gegensatz zu
getSnapshotBeforeUpdate
wirdwillUpdate
vorrender
aufgerufen - Durch Änderungen an reaktiven Properties in „
willUpdate
“ wird der Aktualisierungszyklus nicht noch einmal ausgelöst - Hier können Property-Werte berechnet werden, die von anderen Properties abhängen und im restlichen Aktualisierungsprozess verwendet werden.
- Diese Methode wird auf dem Server in SSR aufgerufen, daher wird hier nicht empfohlen, auf das DOM zuzugreifen.
update
- Nach dem
willUpdate
angerufen - Im Gegensatz zu
getSnapshotBeforeUpdate
wirdupdate
vorrender
aufgerufen. - Änderungen an reaktiven Properties in
update
lösen den Aktualisierungsvorgang nicht noch einmal aus, wenn sie vor dem Aufruf vonsuper.update
vorgenommen werden. - Guter Ort zum Erfassen von Informationen aus dem DOM, das die Komponente umgibt, bevor die gerenderte Ausgabe an das DOM übergeben wird
- Diese Methode wird auf dem Server in SSR nicht aufgerufen.
Andere Lit Lifecycle-Callbacks
Es gibt mehrere Lebenszyklus-Callbacks, die im vorherigen Abschnitt nicht erwähnt wurden, da es in React keine entsprechenden Callbacks gibt. Diese sind:
attributeChangedCallback
Es 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 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 documentFragment
eines HTMLTemplateElement
in die Haupt-document
. Dieser Rückruf ist ebenfalls Teil der Spezifikation für benutzerdefinierte Elemente und sollte nur für erweiterte Anwendungsfälle verwendet werden, wenn die Komponente Dokumente ändert.
Weitere Lebenszyklusmethoden und ‑eigenschaften
Diese Methoden und Eigenschaften sind Klassenmitglieder, die Sie aufrufen, überschreiben oder abwarten können, um den Lebenszyklus zu beeinflussen.
updateComplete
Dies ist ein Promise
, der aufgelöst wird, wenn die Aktualisierung des Elements abgeschlossen ist, da die Aktualisierungs- und Renderingzyklen 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, wenn updateComplete
aufgelöst wird. Dies ist häufig der Fall, wenn eine Komponente eine untergeordnete Komponente rendert und ihre Renderzyklen synchronisiert werden müssen. Beispiele:
class MyElement extends LitElement {
...
async getUpdateComplete() {
await super.getUpdateComplete();
await this.myChild.updateComplete;
}
}
performUpdate
Über diese Methode werden die Lifecycle-Callbacks für die Aktualisierung aufgerufen. Dies sollte im Allgemeinen nicht erforderlich sein, außer in seltenen Fällen, in denen die Aktualisierung synchron oder im Rahmen einer benutzerdefinierten Planung erfolgen muss.
hasUpdated
Diese Property hat den Wert true
, wenn die Komponente mindestens einmal aktualisiert wurde.
isConnected
Diese Eigenschaft ist Teil der Spezifikation für benutzerdefinierte Elemente und lautet true
, wenn das Element derzeit an die Hauptdokumentenstruktur angehängt ist.
Visualisierung des Lebenszyklus von Lit-Aktualisierungen
Der Aktualisierungszyklus besteht aus drei Teilen:
- Vor der Aktualisierung
- Aktualisieren
- Nach der Aktualisierung
Vor dem Update
Nach dem requestUpdate
wird ein geplantes Update erwartet.
Aktualisieren
Nach dem Update
Hooks
Warum-Hooks
In React wurden Hooks für einfache Anwendungsfälle von Funktionskomponenten eingeführt, bei denen ein Zustand erforderlich war. In vielen einfachen Fällen sind Funktionskomponenten mit Hooks in der Regel viel einfacher und übersichtlicher als ihre Klassenkomponenten. Bei der Einführung asynchroner Statusaktualisierungen und der Weitergabe von Daten zwischen Hooks oder Effekten reicht das Hook-Muster jedoch tendenziell nicht aus, und eine klassenbasierte Lösung wie reaktive Controller glänzen in der Regel.
API-Anfrage-Hooks und -Controller
Es ist üblich, einen Hook zu schreiben, der Daten von einer API anfordert. Nehmen wir als Beispiel diese React-Funktionskomponente, die Folgendes ausführt:
index.tsx
- Rendert Text
- Antwort von
useAPI
wird gerendert- Nutzer-ID + Nutzername
- Fehlermeldung
- 404-Fehler, wenn Nutzer 11 erreicht wird (beabsichtigt)
- Fehler beim Abbrechen des API-Abrufs
- Benachrichtigung zum Laden
- Rendert eine Aktionsschaltfläche
- Nächster Nutzer: ruft die API für den nächsten Nutzer ab
- Abbrechen: Dadurch wird der API-Abruf abgebrochen und ein Fehler angezeigt.
useApi.tsx
- Definiert einen benutzerdefinierten
useApi
-Hook - Ruft ein Nutzerobjekt asynchron von einer API ab
- Sendet:
- Nutzername
- Gibt an, ob die Daten abgerufen werden
- Alle Fehlermeldungen
- Callback zum Abbrechen des Abrufs
- Bricht Abrufe ab, wenn sie getrennt werden
- Definiert einen benutzerdefinierten
Hier sehen Sie die Implementierung des Lit + Reactive Controllers.
Fazit:
- Reaktive Controller ähneln am ehesten benutzerdefinierten Hooks.
- Nicht renderbare Daten zwischen Callbacks und Effekten übergeben
- React verwendet
useRef
, um Daten zwischenuseEffect
unduseCallback
zu übergeben - Lit verwendet eine private Klasseneigenschaft
- React ist im Wesentlichen das Nachahmen des Verhaltens einer Privatklassen-Property
- React verwendet
Wenn Ihnen die Syntax der React-Funktionskomponente mit Hooks gefällt, Sie aber die serverlose Umgebung von Lit bevorzugen, empfiehlt das Lit-Team die Haunted-Bibliothek.
Children
Standard-Slot
Wenn HTML-Elementen kein slot
-Attribut zugewiesen ist, werden sie dem standardmäßigen unbenannten Steckplatz zugewiesen. Im folgenden Beispiel fügt MyApp
einen Absatz in einen benannten Slot ein. Der andere Absatz wird standardmäßig in den unbenannten Slot verschoben.“
@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 nachgeordneten Slots ändert, wird ein slotchange
-Ereignis ausgelöst. Eine Lit-Komponente kann einen Ereignis-Listener an ein slotchange
-Ereignis binden. Im folgenden Beispiel werden die assignedNodes des ersten Slots, der in shadowRoot
gefunden wird, in der Konsole unter slotchange
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>
`;
}
}
Verweise
Referenzerstellung
Sowohl Lit als auch React geben nach dem Aufruf ihrer render
-Funktionen eine Referenz auf einen HTMLElement zurück. Es lohnt sich jedoch, 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, die React-Komponenten und keine HTML-Elemente erstellt. Da ein Ref deklariert wird, bevor ein HTMLElement gerendert wird, wird ein Leerzeichen im Arbeitsspeicher zugeordnet. Aus diesem Grund wird null
als Anfangswert einer Ref angezeigt, 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, sucht es in der ReactComponent nach einem Attribut namens ref
. Sofern verfügbar, platziert ReactDOM den Verweis des HTMLElements auf ref.current
.
LitElement verwendet die Vorlagen-Tag-Funktion html
aus lit-html, um ein Template-Element zu erstellen. LitElement prägt den Inhalt der Vorlage nach dem Rendern in das Shadow DOM eines benutzerdefinierten Elements ein. Das Schatten-DOM ist eine beschränkte DOM-Baumstruktur, die von einem Schattenstamm gekapselt wird. Der @query
-Dekorator erstellt dann einen Getter für die Property, der im Wesentlichen eine this.shadowRoot.querySelector
auf dem übergeordneten Root ausführt.
Mehrere Elemente abfragen
Im folgenden Beispiel gibt der @queryAll
-Dekorator die beiden Absätze im Schatten-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 Wesentlichen erstellt @queryAll
einen Getter für paragraphs
, der die Ergebnisse von this.shadowRoot.querySelectorAll()
zurückgibt. In JavaScript kann ein Getter für denselben Zweck deklariert werden:
get paragraphs() {
return this.renderRoot.querySelectorAll('p');
}
Elemente, die die Abfrage ändern
Der @queryAsync
-Decorator eignet sich besser für die Verarbeitung eines Knotens, der sich je nach Status einer anderen Elementeigenschaft ändern kann.
Im folgenden Beispiel findet @queryAsync
das erste Absatzelement. Ein Absatzelement wird jedoch nur gerendert, wenn renderParagraph
zufällig eine ungerade Zahl generiert. Die @queryAsync
-Direktive gibt ein Promise zurück, das aufgelöst wird, sobald 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. Es empfiehlt sich, sich nicht auf den Zustand der Elemente zu verlassen. Das DOM ist einfach eine Folge des Rendering-Prozesses.
Externer Status
Sie können neben Lit auch Redux, MobX oder eine andere Bibliothek zur Zustandsverwaltung verwenden.
Lit-Komponenten werden im Browserbereich erstellt. Jede Bibliothek, die auch im Browserbereich vorhanden ist, ist für Lit verfügbar. Es wurden viele erstaunliche Bibliotheken entwickelt, um die bestehenden staatlichen Verwaltungssysteme in Lit.
Hier ist eine Reihe von Vaadin, in der erläutert wird, wie Redux in einer Lit-Komponente genutzt wird.
Sehen Sie sich lit-mobx von Adobe an, um zu erfahren, wie MobX in Lit auf einer großen Website genutzt werden kann.
Sehen Sie sich auch Apollo Elements an, um zu sehen, 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. Schattenwurzeln generieren einen Schattenbaum, der vom Hauptdokumentenbaum getrennt ist. Das bedeutet, dass die meisten Stile auf dieses Dokument angewendet werden. Bestimmte Stile wie Farben und andere schriftartbezogene Stile können jedoch durchdringen.
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 freigeben
Mit Lit können Sie Stile ganz einfach in Form von CSSTemplateResults
über css
-Vorlagen-Tags für Komponenten freigeben. 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
Schattenwurzeln stellen eine kleine Herausforderung für herkömmliche Themen dar, bei denen in der Regel Top-down-Tag-Ansätze verwendet werden. Die herkömmliche Methode zur Gestaltung von Webkomponenten mit Shadow DOM besteht darin, eine Style-API über benutzerdefinierte CSS-Properties bereitzustellen. Dies ist beispielsweise 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 ändert dann das Design der Website, indem er benutzerdefinierte Eigenschaftswerte anwendet:
html {
--mdc-theme-primary: #F00;
}
html[dark] {
--mdc-theme-primary: #F88;
}
Wenn das Top-down-Design unverzichtbar ist und Sie keine Stile bereitstellen können, können Sie Shadow DOM jederzeit deaktivieren. Überschreiben Sie dazu createRenderRoot
und geben Sie this
zurück. Dadurch wird die Vorlage Ihrer Komponenten in das benutzerdefinierte Element selbst gerendert, anstatt in einen Schattenstamm, der an das benutzerdefinierte Element angehängt ist. Dadurch gehen Sie verloren: Stilkapselung, DOM-Kapselung und Slots.
Produktion
IE 11
Wenn Sie ältere Browser wie IE 11 unterstützen möchten, müssen Sie einige Polyfills laden, die etwa 33 KB groß sind. Weitere Informationen finden Sie hier.
Bedingte Sets
Das Lit-Team empfiehlt, zwei verschiedene Bundles bereitzustellen, eines für IE 11 und eines für moderne Browser. Dies hat mehrere Vorteile:
- Das Bereitstellen von ES 6 ist schneller und wird den meisten Ihrer Kunden dienen.
- Transpiliertes ES 5 erhöht die Bundle-Größe erheblich
- Bedingte Sets bieten das Beste aus beiden Welten
- IE 11-Unterstützung
- Keine Verlangsamung in modernen Browsern
Weitere Informationen zum Erstellen eines bedingt bereitgestellten Bundles finden Sie hier in der Dokumentation.