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 kannst du verschiedene React-Konzepte in Lit übersetzen, z. B.:
- JSX und Vorlagen
- Komponenten und Requisiten
- Status & 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 Chrome-Entwicklertools.
- Wissen über React
- (Fortgeschritten) Für die bestmögliche Entwicklung laden Sie VS Code herunter. Sie benötigen außerdem lit-plugin für VS Code und NPM.
2. Lit vs. React
Die Kernkonzepte und -funktionen von Lit ähneln in vielerlei Hinsicht dem von React, es gibt aber auch einige wesentliche Unterschiede und Unterscheidungsmerkmale:
Es ist klein
Im Vergleich zu React + ReactDOM, das über 40 KB verfügt, sind nur etwa 5 KB verkleinert und mit gzip komprimiert.
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.
Benötigt keine Kreation
Durch neue Browserfunktionen wie ES-Module und getaggte Vorlagenliterale muss keine Kompilierung ausgeführt werden. Das bedeutet, dass Entwicklungsumgebungen mit einem Skript-Tag, einem Browser und einem Server eingerichtet werden können und Sie sofort loslegen 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 jedoch weiteren Lit-Code erstellen und optimieren. Die jüngste Konsolidierung der Entwickler um native ES-Module war für Lit – Lit ist einfach nur normales JavaScript und es werden keine Framework-spezifischen Befehlszeilen oder Build-Handhabungen benötigt.
Unabhängig vom Framework
Die Komponenten von Lit basieren auf einer Reihe von Webstandards, den sogenannten Webkomponenten. Das bedeutet, dass die Erstellung einer Komponente in Lit für aktuelle und zukünftige Frameworks funktioniert. Wenn HTML-Elemente unterstützt werden, werden auch Webkomponenten unterstützt.
Die einzigen Probleme mit der Framework-Interoperabilität treten auf, wenn die Frameworks eine restriktive 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 sehen Sie unter Benutzerdefinierte Elemente überall, mit welchen Frameworks und Bibliotheken benutzerdefinierte Elemente gut funktionieren.
Erstklassige TypeScript-Unterstützung
Sie können Ihren gesamten Lit-Code zwar in JavaScript schreiben, Lit ist jedoch in TypeScript geschrieben und das Lit-Team empfiehlt Entwicklern, auch 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 zur Prüfung Ihrer Komponenten für Ihren Browser keine Tools oder Erweiterungen installieren müssen.
Sie können einfach die Entwicklertools öffnen, ein Element auswählen und sich seine Eigenschaften oder Status ansehen.
Bei der Entwicklung wurde das serverseitige Rendering (SSR) berücksichtigt.
Lit 2 wurde für die SSR-Unterstützung entwickelt. 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 erwartet, diese Tools bald extern auf GitHub zu veröffentlichen.
In der Zwischenzeit kannst du den Fortschritt des Lit-Teams hier verfolgen.
Die Zustimmung ist gering
Für die Nutzung von Lit ist keine große Verpflichtung erforderlich. Sie können Komponenten in Lit erstellen und Ihrem bestehenden Projekt hinzufügen. Wenn Sie das nicht möchten, müssen Sie nicht die gesamte App auf einmal konvertieren, da die Webkomponenten in anderen Frameworks funktionieren.
Hast du eine ganze App in Lit erstellt und möchtest du sie ändern? Dann können Sie Ihre aktuelle Lit-Anwendung in Ihr neues Framework einfügen und beliebige Komponenten zu den Komponenten des neuen Frameworks migrieren.
Darüber hinaus unterstützen viele moderne Frameworks die Ausgabe in Webkomponenten, sodass sie normalerweise selbst in ein Lit-Element passen.
3. Einrichtung und Playground erkunden
Für dieses Codelab gibt es zwei Möglichkeiten:
- Sie können alles online über den Browser erledigen.
- (Fortgeschritten) Mit VS Code auf dem lokalen Computer
Auf den Code zugreifen
Im gesamten Codelab finden Sie Links zum Lit Playground:
Der Playground ist eine Code-Sandbox, die vollständig in deinem Browser ausgeführt wird. Sie kann TypeScript- und JavaScript-Dateien kompilieren und ausführen und außerdem Importe in Knotenmodule automatisch auflösen. z.B.
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://cdn.skypack.dev/lit';
Du kannst das gesamte Tutorial im Lit Playground durchgehen und diese Checkpoints als Ausgangspunkt verwenden. Wenn Sie VS Code verwenden, können Sie diese Checkpoints nutzen, um den Startcode für einen beliebigen Schritt herunterzuladen und damit Ihre Arbeit zu überprüfen.
Erkundung der beleuchteten Spielplatz-Benutzeroberfläche
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)
Dies sind die Vorteile dieser VS Code-Einrichtung:
- Prüfung des Vorlagentyps
- Template Intellisense & automatische Vervollständigung
Wenn Sie NPM und VS Code (mit dem lit-plugin-Plug-in) bereits installiert haben und wissen, wie Sie diese Umgebung verwenden können, können Sie diese Projekte einfach herunterladen und starten. Gehen Sie dazu so vor:
- Klicken Sie auf die Schaltfläche zum Herunterladen.
- Inhalt der TAR-Datei in ein Verzeichnis extrahieren
- (Bei Fehlerbehebung) Richten Sie eine schnelle tsconfig ein, 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 Beleuchtete Vorlagen
JSX ist eine Syntaxerweiterung für JavaScript, mit der React-Nutzer ganz einfach Vorlagen in ihren 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 einen enthaltenen "name". . 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
);
Beachten Sie, dass Lit-Vorlagen kein React-Fragment benötigen, um mehrere Elemente in seinen 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 akzeptieren, die als TemplateResult
bezeichnet werden. Setzen Sie beispielsweise name
in kursiv (<i>
) Tags und umschließen Sie ihn mit einem getaggten Vorlagenliteral Hinweis. Verwenden Sie dabei ein Graviszeichen (`
) und nicht ein einfaches 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
);
Lit-TemplateResult
s können Arrays, Strings, andere TemplateResult
s sowie Anweisungen akzeptieren.
Versuchen Sie für eine Übung, den folgenden React-Code in Lit umzuwandeln:
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 Syntax von JSX und Lit ist die Syntax für die Datenbindung. Nehmen wir als Beispiel diese 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 ausführt:
- Setzt eine definierte Variable auf „Deaktiviert“ (in diesem Fall „false“)
- Legt die Klasse auf
static-class
plus eine Variable fest (in diesem Fall"static-class my-class"
) - Standardwert festlegen
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.
Dann gibt es eine direkte Bindung an das Attribut class
statt 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 das Attribut value
für 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 Lit-Prop-Bindung
html`<my-element ?attribute-name=${booleanVar}>`;
- Das Präfix
?
ist die Bindungssyntax zum Ein-/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 eines Attributs eines Elements - Entspricht
inputRef.propertyName = anyVar
- Gut zur Weitergabe komplexer Daten wie Objekte, Arrays oder Klassen
html`<my-element attribute-name=${stringVar}>`;
- Bindung an das Attribut eines Elements
- 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 ausführt:
- Das Wort „Klick“ protokollieren wenn auf die Eingabe geklickt wird
- Wert der Eingabe protokollieren, wenn der Nutzer ein Zeichen eingibt
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
@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.
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 Event-Listener - Entspricht
inputRef.addEventListener('event-name', ...)
- Nutzt native DOM-Ereignisnamen
5. Komponenten und Requisiten
In diesem Abschnitt lernen Sie die Komponenten und Funktionen der Lit-Klasse kennen. 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 ist eine Kombination der Eigenschaften und des Zustands 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 ein
name
- Legt den Standardwert von
name
auf einen leeren String fest (""
) - Weist
name
neu zu"Elliott"
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 ein Attribut (standardmäßig) sowie eine Eigenschaft Ihrer Komponente bereit
- Definiert, wie das Attribut der Komponente (bei dem es sich um Strings handelt) in einen Wert übersetzt 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>`
}
- Wird immer dann aufgerufen, wenn eine reaktive Eigenschaft geändert wird
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
...
}
- Hiermit wird der Name eines HTML-Element-Tags mit einer Klassendefinition verknüpft.
- Aufgrund des Standards 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 zum TS-Decorator
@customElement
.
<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ä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. Status & Lebenszyklus
In diesem Abschnitt erfahren Sie mehr über den Status und Lebenszyklus von Lit.
Status
Konzept von Lit zu „Reaktiven Eigenschaften“ ist eine Mischung aus Status und Requisiten von React. Wenn reaktive Eigenschaften geändert werden, kann dadurch der Lebenszyklus der Komponente 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 durch
@property
- Ähnlich wie die Eigenschaften und den Zustand von React, aber veränderlich
- Öffentliche API, auf die Nutzer der Komponente zugreifen und sie festlegen können
Interner reaktiver Zustand
// 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 von React, aber veränderbar
- Privater interner Status, auf den normalerweise über die Komponente oder Unterklassen zugegriffen wird
Lifecycle
Der Lit-Lebenszyklus ist dem von React sehr ähnlich, es gibt jedoch einige wesentliche 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';
}
}
- Lit-Äquivalent ist ebenfalls
constructor
- Es muss nichts an den Super Call übergeben werden.
- Aufgerufen durch (nicht vollständig inklusiv):
document.createElement
document.innerHTML
new ComponentClass()
- Befindet sich auf der Seite 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>`;
}
- Lit-Äquivalent ist ebenfalls
render
- Kann jedes Rendering-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 aufgerufen, wenn die Vorlage der Komponente zum ersten Mal im Stamm der Komponente gerendert wird
- Wird nur aufgerufen, wenn das Element verbunden ist, z.B. erst über
document.createElement('my-component')
aufgerufen, bis dieser Knoten an die DOM-Struktur angehängt wird - Hier können Sie die Komponenten so einrichten, dass das von der Komponente gerenderte DOM erforderlich ist.
- Im Gegensatz zu den
componentDidMount
-Änderungen an den reaktiven Eigenschaften von React infirstUpdated
führen die Änderungen zu einem neuen Rendering, obwohl der Browser die Änderungen in der Regel im selben Frame stapelt. Wenn diese Änderungen keinen Zugriff auf das Stamm-DOM erfordern, sollten sie normalerweise inwillUpdate
vorgenommen werden.
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 „verbunden“ werden mehrmals
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
kann vorfirstUpdated
aufgerufen werden, sodass beim ersten Aufruf möglicherweise kein DOM verfügbar ist.
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 kleine Äquivalent ist
updated
(in der englischen Vergangenheitsform „update“) - Im Gegensatz zu React wird
updated
auch beim ersten Rendering aufgerufen. - Ähnlich wie
componentDidUpdate
von 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 werden benutzerdefinierte Elemente, die vom DOM getrennt werden, nicht zerstört.
- Im Gegensatz zu
componentWillUnmount
wirddisconnectedCallback
aufgerufen, nachdem das Element aus der Baumstruktur entfernt wurde - 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 ausführt:
- Sie rendert „Hello World! Es ist“ und zeigt dann die Uhrzeit an,
- Jede Sekunde wird die Uhr aktualisiert
- Nach dem Abnehmen wird das Intervall gelöscht, das die Markierung markiert.
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);
Als Nächstes rendern Sie 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 der Dokumentstruktur entfernt wird. Wenn es also wieder angehängt wird, muss das Intervall von vorn beginnen. 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 von der Dokumentstruktur 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
Reaktions-Hooks ermöglichen es, Funktionskomponenten in einen Zustand verwandelt. Dies hat mehrere Vorteile.
- Sie vereinfachen die Wiederverwendung von zustandsorientierter 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 unsaubere Initialisierung von Eigenschaften im
constructor
- Dies war ein Grund, der damals vom React-Team genannt wurde, aber bis ES2019 behoben wurde
- Probleme, die dadurch verursacht werden, dass sich
this
nicht mehr auf die Komponente bezieht
React-Hook-Konzepte in Lit
Wie im Abschnitt Komponenten und Props enthält, 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 diese Probleme an?
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 gibt es in Lit als reaktive Controller. Reaktive Controllermuster ermöglichen das Teilen einer zustandsorientierten Logik, das Aufteilen von Komponenten in kleinere, modularere Bits sowie die Einbindung in den Aktualisierungslebenszyklus eines Elements.
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 parallel zum Host aufgerufen. Rufen Sie sich zum Beispiel das Uhrenbeispiel aus der Spalte State & Abschnitt zum 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:
- Sie rendert „Hello World! Es ist“ und zeigt dann die Uhrzeit an,
- Jede Sekunde wird die Uhr aktualisiert
- Nach dem Abnehmen wird das Intervall gelöscht, das die Markierung markiert.
Bau des Gerüsts der Komponente
Beginnen Sie mit der Deklaration der Komponentenklasse und fügen Sie die Funktion render
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-Lebenszyklus-Callbacks in Controller-Callbacks übersetzen. Kurzum:
componentDidMount
- In
connectedCallback
von LitElement - An
hostConnected
des Controllers
- In
ComponentWillUnmount
- In
disconnectedCallback
von LitElement - An
hostDisconnected
des Controllers
- In
Weitere Informationen zur Übersetzung des React-Lebenszyklus in den Lit-Lebenszyklus findest du im Artikel zu 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 unter Status & 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 du den Uhr-Controller verwenden möchtest, importiere den Controller und aktualisiere 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);
Zur Verwendung des Controllers müssen Sie den Controller instanziieren. Dazu übergeben Sie einen Verweis auf den Controller-Host (die Komponente <my-element>
) und dann den Controller in der render
-Methode verwenden.
Erneute Renderings im Controller auslösen
Beachten Sie, dass hier die Uhrzeit angezeigt wird, diese aber nicht aktualisiert wird. Das liegt daran, dass der Controller das Datum jede Sekunde festlegt, der Host jedoch nicht aktualisiert. Das liegt daran, dass sich date
in der Klasse ClockController
und nicht mehr in der Komponente ändert. Das bedeutet, dass nach dem Festlegen von date
auf dem Controller der Host 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();
}
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 Zusammensetzung, da Sie Komponenten verschachteln können.
In React werden untergeordnete Elemente durch Requisiten vererbt. Die Standardfläche ist props.children
und die render
-Funktion definiert, wo die Standardfläche positioniert wird. Beispiel:
const MyArticle = (props) => {
return <article>{props.children}</article>;
};
Denken Sie daran, dass props.children
React-Komponenten und keine HTML-Elemente sind.
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 Anzeigenflächen 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 Sie weitere <slot>
-Elemente hinzufügen, entstehen mehr Flächen in Lit. Mit dem name
-Attribut <slot name="slot-name">
werden mehrere Anzeigenflächen definiert. So können untergeordnete Elemente deklarieren, welcher Slot ihnen zugewiesen wird.
@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>
`;
}
}
Standard-Anzeigenfläche
Slots zeigen ihre Unterstruktur an, wenn für diesen Slot keine Knoten prognostiziert wurden. 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 Anzeigenflächen über die Eigenschaften einer Komponente untergeordnete Elemente zugewiesen. Im folgenden Beispiel 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 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 muss ein 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 rendert HTMLElements.
In React stellen Refs den Speicherplatz im Speicher dar, 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:
- Leere Texteingabe und Schaltfläche mit Text rendern
- Eingabe fokussieren, wenn auf die Schaltfläche geklickt wird
Nach dem ersten Rendering setzt React inputRef.current
auf die generierte HTMLInputElement
über das Attribut ref
.
Lit „Referenzen“ mit @query
Die Bibliothek befindet sich in der Nähe des Browsers 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
(erstellen eines Getters für einenHTMLInputElement
). - Deklariert einen Klickereignis-Callback namens
onButtonClick
und hängt ihn an. - Fokussiert die Eingabe beim Klicken auf die Schaltfläche
In JavaScript führen die @query
- und @queryAll
-Decorators querySelector
und querySelectorAll
aus. Entspricht dem JavaScript-Äquivalent zu @query('input') inputEl!: HTMLInputElement;
get inputEl() {
return this.renderRoot.querySelector('input');
}
Nachdem die Lit-Komponente die Vorlage der render
-Methode dem Stamm von my-element
per Commit übergeben hat, ermöglicht der @query
-Decorator inputEl
, das erste input
-Element im Renderingstamm zurückzugeben. Es wird null
zurückgegeben, wenn @query
das angegebene Element nicht finden kann.
Wenn der Renderingstamm mehrere input
-Elemente enthält, würde @queryAll
eine Liste mit Knoten zurückgeben.
10. Vermittlungsstatus
In diesem Abschnitt erfahren Sie, wie Sie den Status zwischen Komponenten in Lit.
Wiederverwendbare Komponenten
React ahmt funktionale Rendering-Pipelines mit einem Top-down-Datenfluss nach. Eltern stellen ihren Kindern mit Hilfsmitteln einen Zustand zur Verfügung. Kinder können über Rückrufe in Requisiten mit ihren Eltern kommunizieren.
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 eine Schaltfläche mit dem Label „+step“ oder „-step“
- Aktualisiert die übergeordnete Komponente durch Aufrufen von
props.addToCounter
mitprops.step
als Argument beim Klick
Obwohl es möglich ist, Callbacks in Lit zu übergeben, sind die herkömmlichen Muster anders. Die React-Komponente im obigen Beispiel könnte im folgenden Beispiel 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 obigen Beispiel führt eine Lit-Komponente Folgendes aus:
- Erstellen Sie die reaktive Property
step
. - Löst ein benutzerdefiniertes Ereignis namens
update-counter
aus, das beim Klick den Wertstep
des Elements enthält.
Browserereignisse werden von untergeordneten Elementen zu übergeordneten Elementen übergehen. Mithilfe von Ereignissen können untergeordnete Publisher Interaktionsereignisse und Statusänderungen senden. React übergibt den Zustand grundsätzlich in die entgegengesetzte Richtung. Daher ist es ungewöhnlich, dass React-Komponenten Ereignisse auf dieselbe Weise auslösen und überwachen wie Lit-Komponenten.
Zustandsorientierte Komponenten
In React werden zum Verwalten des Status häufig Hooks verwendet. Eine MyCounter
-Komponente kann durch Wiederverwenden 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
kann in Lit. 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 Eigenschaft namens
count
, die die Komponente aktualisiert, wenn sich der Wert ändert - Bindet den
addToCounter
-Callback an den@update-counter
-Event-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. Es hat sich auch bewährt, das Ereignissystem des Browsers zu verwenden, um Details von unten nach oben auf einer Karte darzustellen.
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
Es gibt mehrere Möglichkeiten, Elemente zu gestalten, sowie eine integrierte Lösung.
Inline-Stile
Lit unterstützt Inline-Stile sowie Bindungen 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 und binden Sie einen Rahmen von border-color.js
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. Lit bietet daher eine entsprechende Anweisung.
styleMap
Die Anweisung 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>
`;
}
}
Im obigen Beispiel geschieht Folgendes:
- Zeigt ein
h1
mit Rahmen und einer Farbauswahl an - Ändert den
border-color
in den Wert aus der Farbauswahl
Außerdem wird styleMap
verwendet, um die Stile von h1
festzulegen. styleMap
hat eine ähnliche Syntax wie die style
-Attributbindungssyntax von React.
CSSResult
Die empfohlene Methode für den Stil von Komponenten ist die Verwendung des mit css
getaggten Vorlagenliterals.
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 der Verwendung des Vorlagen-Tags css
:
- Einmal pro Klasse vs. pro Instanz geparst
- Bei der Implementierung wurde die Wiederverwendbarkeit der Module berücksichtigt.
- Können Stile einfach in eigene Dateien aufteilen
- Kompatibel mit dem Polyfill für benutzerdefinierte CSS-Eigenschaften
Beachten Sie außerdem das Tag <style>
in index.html
:
<!-- index.html -->
<style>
h1 {
color: red !important;
}
</style>
Die Beleuchtet zeigt den Umfang Ihrer Komponenten an bis hin zu ihren Wurzeln. 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 Browser-Deduplizierung von CSSResult
-Werten 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 & Virtuelles DOM
Lit-HTML enthält kein konventionelles virtuelles DOM, das die einzelnen Knoten unterscheidet. Stattdessen werden Leistungsmerkmale genutzt, die in der Spezifikation des Tagged-Vorlagenliterals von ES2015 unverzichtbar sind. Getaggte Vorlagenliterale sind Vorlagenliteralstrings, an die Tag-Funktionen angehängt sind.
Hier ist 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 Stringarrays, die an die Tag-Funktion übergeben werden, 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). Die Bindung, das Parsen und das Vorlagen-Caching von Lit können diese Funktionen nutzen, ohne dass sich der Aufwand für die Laufzeit verändert.
Dieses integrierte Browserverhalten von getaggten Vorlagenliteralen verschafft Lit einen erheblichen Leistungsvorteil. Die meisten herkömmlichen virtuellen DOMs erledigen den Großteil ihrer Arbeit in JavaScript. Getaggte Vorlagenliterale haben jedoch die meisten Unterschiede in der C++ 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 & JSX + React DOM
JSX wird nicht im Browser ausgeführt, sondern nutzt einen Präprozessor, um JSX in JavaScript-Funktionsaufrufe zu konvertieren (normalerweise ü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. Das bedeutet, dass Sie für die ersten Schritte mit Lit nur eine HTML-Datei, ein ES-Modulskript und einen Server benötigen. Hier ist ein vollständig im Browser ausführbares Skript:
<!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 sowie beim Auslösen von Ereignissen weniger das Parsen der Laufzeit durchgeführt wird.
Komponenten und 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.
Benutzerdefinierte Elemente bieten als Komponentensystem einige Vorteile:
- Nativ im Browser und ohne zusätzliche 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 „hydrate“ registriert werden DOM
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:Obwohl die Entwickler von Browserkomfort-Anbietern die selbstschließende Tag-Spezifikation bereuen, enthalten neuere Spezifikationen tendenziell 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
Lit hat benutzerdefinierte Elemente anstelle eines maßgeschneiderten Elementsystems verwendet, da die benutzerdefinierten Elemente in den Browser integriert sind, und das Lit-Team ist der Meinung, dass die Vorteile des plattformübergreifenden Frameworks die Vorteile einer Komponentenabstraktionsschicht überwiegen. Die Bemühungen des Lit-Teams im Lit-SSR-Bereich haben die Hauptprobleme bei der JavaScript-Registrierung überwunden. Darüber hinaus nutzen einige Unternehmen wie GitHub die Lazy-Registrierung für benutzerdefinierte Elemente, um Seiten schrittweise mit optionalem Flair zu verbessern.
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 – angesichts der folgenden LitElement-Definition:
// 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 werden Daten an dieses benutzerdefinierte Element übergeben:
<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 & Lebenszyklus
Andere React-Lebenszyklus-Callbacks
static getDerivedStateFromProps
Es gibt kein Äquivalent in Lit, da Requisiten und Zustand beide die gleichen Klasseneigenschaften sind.
shouldComponentUpdate
- Liquiditätsäquivalent ist
shouldUpdate
- Wird im Gegensatz zu React beim ersten Rendering aufgerufen
- Ähnlich wie
shouldComponentUpdate
von React
getSnapshotBeforeUpdate
Im Feld „Lit“ entspricht getSnapshotBeforeUpdate
sowohl update
als auch willUpdate
willUpdate
- Vor
update
angerufen - Im Gegensatz zu
getSnapshotBeforeUpdate
wirdwillUpdate
vorrender
aufgerufen - Durch Änderungen an reaktiven Properties in „
willUpdate
“ wird der Aktualisierungszyklus nicht noch einmal ausgelöst - Guter Ort zum Berechnen von Attributwerten, die von anderen Attributen abhängen und im weiteren Aktualisierungsprozess verwendet werden
- Diese Methode wird auf dem Server in SSR aufgerufen, daher wird hier nicht empfohlen, auf das DOM zuzugreifen.
update
- Nach
willUpdate
angerufen - Im Gegensatz zu
getSnapshotBeforeUpdate
wirdupdate
vorrender
aufgerufen - Änderungen an reaktiven Eigenschaften in
update
lösen den Aktualisierungszyklus nicht noch einmal aus, wenn sie vor dem Aufruf vonsuper.update
geändert 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. Sie sind:
attributeChangedCallback
Es wird aufgerufen, wenn sich eines der observedAttributes
-Elemente 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. vom documentFragment
eines HTMLTemplateElement
zum Haupt-document
. Dieser Callback ist ebenfalls Teil der Spezifikation für benutzerdefinierte Elemente und sollte nur für erweiterte Anwendungsfälle verwendet werden, wenn die Komponente Dokumente ändert.
Andere Lebenszyklusmethoden und -attribute
Diese Methoden und Attribute sind Klassenmitglieder, die Sie aufrufen, überschreiben oder warten können, um den Lebenszyklusprozess 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 deren Rendering-Zyklen synchron sein müssen. z.B.
class MyElement extends LitElement {
...
async getUpdateComplete() {
await super.getUpdateComplete();
await this.myChild.updateComplete;
}
}
performUpdate
Mit dieser Methode werden die Callbacks für den Aktualisierungslebenszyklus 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 Eigenschaft ist 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 Hauptdokumentstruktur angehängt ist.
Visualisierung des Lebenszyklus von Lit-Updates
Der Aktualisierungszyklus besteht aus drei Teilen:
- Vor der Aktualisierung
- Aktualisieren
- Nach der Aktualisierung
Vorab-Update
Nach dem requestUpdate
wird ein geplantes Update erwartet.
Aktualisieren
Nach Aktualisierung
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 wesentlich einfacher und leichter lesbar als die entsprechenden 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
- Rendert die Antwort
- von
- Nutzer-ID + Nutzername
- Fehlermeldung
- 404, wenn Nutzer 11 erreicht wird (von Grund auf)
- Fehler beim Abbrechen des API-Abrufs
- Benachrichtigung zum Laden
useAPI
- 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
- Ob der Abruf geladen wird
- 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 sind wie benutzerdefinierte Hooks
- Übergabe nicht renderbarer Daten zwischen Callbacks und Effekten
- React verwendet
useRef
, um Daten zwischenuseEffect
unduseCallback
zu übergeben - Lit verwendet eine private Klassen-Property
- React ist im Wesentlichen das Nachahmen des Verhaltens einer privaten Klasseneigenschaft
- React verwendet
Wenn Ihnen die Syntax der React-Funktionskomponente mit Hooks, aber derselben baulosen Umgebung wie Lit gefällt, empfiehlt das Lit-Team außerdem dringend die Haunted-Bibliothek.
Children
Standard-Slot
Wenn HTML-Elementen kein slot
-Attribut zugewiesen ist, werden sie der unbenannten Standardfläche zugewiesen. Im folgenden Beispiel platziert MyApp
einen Absatz in einer benannten Anzeigenfläche. Für den anderen Absatz wird standardmäßig die unbenannte Anzeigenfläche verwendet.
@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 Beispiel unten werden für den ersten Slot, der im shadowRoot
gefunden wird, die assignedNodes 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>
`;
}
}
Verweise
Referenzerstellung
Sowohl Lit als auch React stellen einen Verweis auf ein HTMLElement bereit, nachdem ihre render
-Funktionen aufgerufen wurden. 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 funktionsfähige Pipeline, die React-Komponenten und nicht HTMLElements 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
von Lit-HTML, um ein Vorlagenelement im Hintergrund zu erstellen. LitElement fügt nach dem Rendern den Inhalt der Vorlage 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
-Decorator erstellt dann einen Getter für die Property, der im Grunde eine this.shadowRoot.querySelector
für den Bereichsstamm ausführt.
Mehrere Elemente abfragen
Im folgenden Beispiel gibt der @queryAll
-Decorator die beiden Absätze im Schattenstamm 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 Anweisung @queryAsync
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 Callbacks verwendet, da der Status von React selbst vermittelt wird. Es empfiehlt sich, sich nicht auf den Zustand der Elemente zu verlassen. Das DOM ist einfach eine Auswirkung des Rendering-Prozesses.
Externer Status
Es ist möglich, Redux, MobX oder jede andere State Management Library neben Lit.
Lit-Komponenten werden im Browserbereich erstellt. So ist jede Bibliothek, die auch im Browser-Bereich vorhanden ist, für Lit. 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 und erfahren Sie, wie eine große Website MobX in Lit nutzen kann.
Auf der Website Apollo Elements erfahren Sie außerdem, 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
Schatten-DOM
Um Stile und DOMs nativ in einem benutzerdefinierten Element zu kapseln, verwendet Lit Shadow DOM. Mit Schattenwurzeln wird eine Schattenstruktur erstellt, die vom Hauptdokumentbaum 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.
Shadow DOM führt außerdem neue Konzepte und Selektoren in die CSS-Spezifikation ein:
: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“ ist es einfach, Stile in Form von CSSTemplateResults
über css
-Vorlagen-Tags zwischen Komponenten zu 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
Schattenwurzeln stellen eine Herausforderung für konventionelle Themen dar, bei denen es sich in der Regel um Top-down-Tag-Ansätze handelt. Die herkömmliche Methode zur Behandlung des Designs mit Webkomponenten, die Shadow DOM verwenden, besteht darin, eine Stil-API über benutzerdefinierte CSS-Eigenschaften bereitzustellen. Dies ist zum Beispiel ein Muster, das Material Design verwendet:
.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 das Shadow DOM jederzeit deaktivieren. Überschreiben Sie dazu createRenderRoot
, sodass this
zurückgegeben wird, das dann die Komponenten rendert. an das benutzerdefinierte Element selbst anstatt an einen Schattenstamm an, der an das benutzerdefinierte Element angehängt ist. Dadurch gehen Sie verloren: Stilkapselung, DOM-Kapselung und Slots.
Produktion
IE 11
Falls ältere Browser wie IE 11 unterstützt werden müssen, müssen einige Polyfills geladen werden, deren Größe noch etwa 33 KB beträgt. Weitere Informationen finden Sie hier.
Bedingte Sets
Das Lit-Team empfiehlt die Bereitstellung von zwei verschiedenen Bundles: eines für IE 11 und eines für moderne Browser. Dies hat mehrere Vorteile:
- Die Bereitstellung von ES 6 ist schneller und wird für die meisten Ihrer Kunden nützlich sein.
- Transpiliertes ES 5 erhöht die Bundle-Größe erheblich
- Bedingte Pakete bieten das Beste aus beiden Welten
- Unterstützung für IE 11
- Keine Verlangsamung bei modernen Browsern
Weitere Informationen zum Erstellen eines bedingt bereitgestellten Bundles finden Sie hier.