1. Wprowadzenie
Co to jest Lit
Lit to prosta biblioteka do tworzenia szybkich i lekkich komponentów internetowych, które działają w dowolnej platformie lub bez niej. Za pomocą Lit możesz tworzyć komponenty, aplikacje, systemy projektowe i inne elementy, które można udostępniać.
Czego się nauczysz
Jak przetłumaczyć na Lit kilka pojęć z Reacta, takich jak:
- JSX i szablony
- Komponenty i rekwizyty
- Stan i cykl życia
- Elementy przykuwające uwagę
- Dzieci
- Refs
- Stan pośredniczenia
Co utworzysz
Po ukończeniu tego laboratorium dowiesz się, jak przekształcać koncepcje komponentów React na ich odpowiedniki w Lit.
Czego potrzebujesz
- najnowsza wersja przeglądarki Chrome, Safari, Firefox lub Edge;
- Znajomość HTML-a, CSS-a, JavaScriptu i Narzędzi deweloperskich w Chrome.
- Znajomość Reacta
- (Zaawansowane) Jeśli chcesz mieć jak najlepsze środowisko programistyczne, pobierz VS Code. Potrzebujesz też lit-plugin do VS Code i NPM.
2. Lit a React
Podstawowe koncepcje i możliwości Lit są pod wieloma względami podobne do Reacta, ale Lit ma też kilka kluczowych różnic i wyróżników:
Jest mały
Lit jest niewielki: po zminimalizowaniu i skompresowaniu ma około 5 KB, a React + ReactDOM – ponad 40 KB.

Szybko
W publicznych testach porównawczych, w których system szablonów Lit, czyli lit-html, jest porównywany z VDOM Reacta, lit-html jest o 8–10% szybszy od Reacta w najgorszym przypadku i o ponad 50%szybszy w bardziej typowych zastosowaniach.
LitElement (klasa bazowa komponentów Lit) dodaje do lit-html minimalny narzut, ale pod względem wydajności wyprzedza Reacta o 16–30%, jeśli chodzi o funkcje komponentów, takie jak wykorzystanie pamięci oraz czas interakcji i uruchamiania.

Nie wymaga kompilacji
Dzięki nowym funkcjom przeglądarki, takim jak moduły ES i oznaczone literały szablonów, Lit nie wymaga kompilacji do działania. Oznacza to, że środowiska deweloperskie można skonfigurować za pomocą tagu skryptu, przeglądarki i serwera, a potem od razu zacząć pracę.
W przypadku modułów ES i nowoczesnych sieci CDN, takich jak Skypack czy UNPKG, możesz nawet nie potrzebować NPM, aby zacząć!
Jeśli jednak chcesz, możesz nadal tworzyć i optymalizować kod Lit. Ostatnie ujednolicenie środowisk deweloperskich wokół natywnych modułów ES było korzystne dla Lit – Lit to po prostu zwykły JavaScript i nie ma potrzeby stosowania interfejsów CLI ani obsługi kompilacji specyficznych dla platformy.
Niezależne od platformy
Komponenty Lit są oparte na zestawie standardów internetowych o nazwie Komponenty internetowe. Oznacza to, że komponent utworzony w Lit będzie działać w obecnych i przyszłych platformach. Jeśli obsługuje elementy HTML, obsługuje też komponenty internetowe.
Jedynym problemem z współdziałaniem platform jest ograniczone wsparcie dla DOM. React to jeden z takich frameworków, ale umożliwia on stosowanie obejść za pomocą referencji, a referencje w React nie zapewniają programistom dobrego środowiska pracy.
Zespół Lit pracuje nad eksperymentalnym projektem o nazwie @lit-labs/react, który będzie automatycznie analizować komponenty Lit i generować otoczkę React, dzięki czemu nie trzeba będzie używać odwołań.
Dodatkowo na stronie Custom Elements Everywhere znajdziesz informacje o tym, które frameworki i biblioteki dobrze współpracują z elementami niestandardowymi.
Pełna obsługa TypeScript
Chociaż cały kod Lit można napisać w JavaScript, Lit jest napisany w TypeScript i zespół Lit zaleca, aby deweloperzy również używali TypeScriptu.
Zespół Lit współpracuje ze społecznością Lit, aby pomagać w utrzymywaniu projektów, które zapewniają sprawdzanie typów TypeScript i funkcję IntelliSense w szablonach Lit zarówno w czasie programowania, jak i kompilacji za pomocą lit-analyzer i lit-plugin.


Narzędzia dla programistów są wbudowane w przeglądarkę
Komponenty Lit to po prostu elementy HTML w DOM. Oznacza to, że aby sprawdzić komponenty, nie musisz instalować żadnych narzędzi ani rozszerzeń w przeglądarce.
Wystarczy otworzyć narzędzia deweloperskie, wybrać element i sprawdzić jego właściwości lub stan.

Został zaprojektowany z myślą o renderowaniu po stronie serwera (SSR).
Biblioteka Lit 2 została zaprojektowana z myślą o obsłudze renderowania po stronie serwera. W momencie pisania tego ćwiczenia zespół Lit nie udostępnił jeszcze narzędzi SSR w stabilnej formie, ale już wdrażał komponenty renderowane po stronie serwera w usługach Google i testował SSR w aplikacjach React. Zespół Lit planuje wkrótce udostępnić te narzędzia w GitHubie.
Postępy zespołu Lit możesz śledzić tutaj.
Wymaga niewielkich nakładów
Korzystanie z Lit nie wymaga dużego zaangażowania. Komponenty możesz tworzyć w Lit i dodawać je do istniejącego projektu. Jeśli Ci się nie spodobają, nie musisz od razu konwertować całej aplikacji, ponieważ komponenty internetowe działają w innych platformach.
Czy masz już gotową aplikację w Lit i chcesz ją zmienić na inną? W takim przypadku możesz umieścić obecną aplikację Lit w nowej strukturze i przenieść do komponentów nowej struktury wszystko, co chcesz.
Wiele nowoczesnych platform obsługuje też dane wyjściowe w komponentach internetowych, co oznacza, że zwykle mogą one mieścić się w elemencie Lit.
3. Konfigurowanie i poznawanie Playgroundu
Ten codelab możesz przejść na 2 sposoby:
- Możesz to zrobić w całości online w przeglądarce.
- (Zaawansowane) Możesz to zrobić na komputerze lokalnym za pomocą VS Code.
Dostęp do kodu
W tym przewodniku znajdziesz linki do platformy Lit, np. takie:
Plac zabaw to piaskownica kodu, która działa w całości w przeglądarce. Może kompilować i uruchamiać pliki TypeScript i JavaScript, a także automatycznie rozpoznawać importy do modułów węzła, np.
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://cdn.skypack.dev/lit';
Cały samouczek możesz przejść na platformie Lit, używając tych punktów kontrolnych jako punktów wyjścia. Jeśli używasz VS Code, możesz pobrać kod początkowy dla dowolnego kroku, korzystając z tych punktów kontrolnych. Możesz też używać ich do sprawdzania swojej pracy.
Poznawanie interfejsu podświetlonego placu zabaw

Zrzut ekranu interfejsu Lit Playground z wyróżnionymi sekcjami, których będziesz używać w tym module.
- Selektor plików. Zwróć uwagę na przycisk plusa...
- Edytor plików.
- Podgląd kodu.
- Przycisk Załaduj ponownie.
- Przycisk pobierania.
Konfiguracja VS Code (zaawansowana)
Oto zalety korzystania z tej konfiguracji VS Code:
- Sprawdzanie typu szablonu
- Inteligentne podpowiedzi i autouzupełnianie w szablonach
Jeśli masz już zainstalowane NPM i VS Code (z wtyczką lit-plugin) i wiesz, jak korzystać z tego środowiska, możesz po prostu pobrać te projekty i je uruchomić, wykonując te czynności:
- Naciśnij przycisk pobierania
- Wyodrębnij zawartość pliku tar do katalogu.
- (Jeśli TS) skonfiguruj szybki plik tsconfig, który generuje moduły ES i ES2015+
- Zainstaluj serwer deweloperski, który może rozwiązywać specyfikatory modułów podstawowych (zespół Lit zaleca @web/dev-server).
- Oto przykład:
package.json
- Oto przykład:
- Uruchom serwer deweloperski i otwórz przeglądarkę (jeśli używasz @web/dev-server, możesz użyć
npx web-dev-server --node-resolve --watch --open).- Jeśli używasz przykładu
package.json, użyjnpm run dev
- Jeśli używasz przykładu
4. JSX i szablony
W tej sekcji poznasz podstawy tworzenia szablonów w Lit.
Szablony JSX i Lit
JSX to rozszerzenie składni JavaScript, które umożliwia użytkownikom Reacta łatwe pisanie szablonów w kodzie JavaScript. Szablony Lit służą podobnemu celowi: wyrażaniu interfejsu komponentu jako funkcji jego stanu.
Podstawowa składnia
W Reactcie kod JSX „hello world” renderuje się w ten sposób:
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
);
W przykładzie powyżej znajdują się 2 elementy i zmienna „name”. W Lit wykonasz te czynności:
import {html, render} from 'lit';
const name = 'Josh Perez';
const element = html`
<h1>Hello, ${name}</h1>
<div>How are you?</div>`;
render(
element,
mountNode
);
Zwróć uwagę, że szablony Lit nie wymagają elementu React Fragment do grupowania wielu elementów w szablonach.
W Lit szablony są opakowane w html oznaczony szablon LITeral, od którego pochodzi nazwa Lit.
Wartości szablonu
Szablony Lit mogą akceptować inne szablony Lit, zwane TemplateResult. Na przykład umieść name w tagach kursywy (<i>) i otocz go oznaczonym szablonem literału N.B. Pamiętaj, aby użyć znaku grawisu (`), a nie znaku pojedynczego cudzysłowu (').
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 TemplateResults może akceptować tablice, ciągi znaków, inne TemplateResults oraz dyrektywy.
Spróbuj przekonwertować poniższy kod React na Lit:
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
);
Odpowiedź:
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
);
Przekazywanie i ustawianie właściwości
Jedną z największych różnic między składnią JSX i Lit jest składnia wiązania danych. Weźmy na przykład to pole wejściowe React z powiązaniami:
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
);
W powyższym przykładzie zdefiniowano dane wejściowe, które wykonują te czynności:
- Ustawia wartość zmiennej „disabled” na zdefiniowaną zmienną (w tym przypadku „false”).
- Ustawia klasę na
static-classplus zmienną (w tym przypadku"static-class my-class"). - Ustawia wartość domyślną
W Lit wykonasz te czynności:
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
);
W przykładzie Lit dodano powiązanie logiczne, aby przełączać atrybut disabled.
Następnie następuje powiązanie bezpośrednio z atrybutem class, a nie z atrybutem className. Do atrybutu class można dodać wiele powiązań, chyba że używasz dyrektywy classMap, która jest deklaratywnym pomocnikiem do przełączania klas.
Na koniec wejściu przypisywana jest właściwość value. W przeciwieństwie do Reacta nie spowoduje to ustawienia elementu wejściowego jako tylko do odczytu, ponieważ jest zgodne z natywną implementacją i działaniem elementu wejściowego.
Składnia wiązania lit prop
html`<my-element ?attribute-name=${booleanVar}>`;
- Prefiks
?to składnia wiązania służąca do przełączania atrybutu w elemencie. - Równowartość ceny wynoszącej
inputRef.toggleAttribute('attribute-name', booleanVar) - Przydatne w przypadku elementów, które używają
disabled, ponieważdisabled="false"jest nadal odczytywane przez interfejs DOM jako wartość „true”, ponieważinputElement.hasAttribute('disabled') === true
html`<my-element .property-name=${anyVar}>`;
- Prefiks
.to składnia wiązania służąca do ustawiania właściwości elementu. - Równowartość ceny wynoszącej
inputRef.propertyName = anyVar - Dobre rozwiązanie do przekazywania złożonych danych, takich jak obiekty, tablice lub klasy.
html`<my-element attribute-name=${stringVar}>`;
- Wiązanie z atrybutem elementu
- Równowartość ceny wynoszącej
inputRef.setAttribute('attribute-name', stringVar) - Dobrze sprawdza się w przypadku wartości podstawowych, selektorów reguł stylu i metody querySelector
Przekazywanie modułów obsługi
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
);
W powyższym przykładzie zdefiniowano dane wejściowe, które wykonują te czynności:
- Rejestrowanie słowa „click” po kliknięciu pola
- Rejestrowanie wartości danych wejściowych, gdy użytkownik wpisuje znak
W Lit wykonasz te czynności:
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
);
W przykładzie Lit do zdarzenia click dodano detektor z funkcją @click.
Następnie zamiast onChange użyj powiązania z natywnym input zdarzeniem w <input>, ponieważ natywne change zdarzenie jest wywoływane tylko w blur (React abstrahuje te zdarzenia).
Składnia modułu obsługi zdarzeń Lit
html`<my-element @event-name=${() => {...}}></my-element>`;
- Prefiks
@to składnia wiązania detektora zdarzeń. - Równowartość ceny wynoszącej
inputRef.addEventListener('event-name', ...) - Używa natywnych nazw zdarzeń DOM
5. Komponenty i rekwizyty
W tej sekcji dowiesz się więcej o komponentach i funkcjach klas Lit. Stan i hooki omówimy bardziej szczegółowo w dalszej części tego artykułu.
Komponenty klasowe i LitElement
Odpowiednikiem komponentu klasy React w Lit jest LitElement, a koncepcja „właściwości reaktywnych” w Lit to połączenie właściwości i stanu React. Na przykład:
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
);
W przykładzie powyżej znajduje się komponent React, który:
- Renderuje
name - Ustawia domyślną wartość
namena pusty ciąg znaków (""). - Ponownie przypisuje
namedo"Elliott"
Oto jak to zrobić w LitElement
W 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>`
}
}
W 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);
W pliku HTML:
<!-- index.html -->
<head>
<script type="module" src="./index.js"></script>
</head>
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
Omówienie tego, co się dzieje w przykładzie powyżej:
@property({type: String})
name = '';
- Definiuje publiczną właściwość reaktywną – część publicznego interfejsu API komponentu.
- Udostępnia atrybut (domyślnie) oraz właściwość w komponencie.
- Określa, jak przetłumaczyć atrybut komponentu (który jest ciągiem znaków) na wartość.
static get properties() {
return {
name: {type: String}
}
}
- Spełnia tę samą funkcję co dekorator
@propertyTS, ale działa natywnie w JavaScript.
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
- Jest ona wywoływana za każdym razem, gdy zmieni się dowolna właściwość reaktywna.
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
...
}
- Powiązanie nazwy tagu elementu HTML z definicją klasy
- Zgodnie ze standardem Custom Elements nazwa tagu musi zawierać łącznik (-).
thisw LitElement odnosi się do instancji elementu niestandardowego (w tym przypadku<welcome-banner>).
customElements.define('welcome-banner', WelcomeBanner);
- Jest to odpowiednik dekoratora
@customElementTS w JavaScript.
<head>
<script type="module" src="./index.js"></script>
</head>
- Importuje definicję elementu niestandardowego.
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
- Dodaje element niestandardowy do strony.
- Ustawia właściwość
namena'Elliott'.
Komponenty funkcyjne
Lit nie ma odpowiednika funkcji komponentu, ponieważ nie używa JSX ani preprocesora. Można jednak łatwo utworzyć funkcję, która przyjmuje właściwości i renderuje DOM na ich podstawie. Na przykład:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Elliott"/>
ReactDOM.render(
element,
mountNode
);
W języku Lit wyglądałoby to tak:
import {html, render} from 'lit';
function Welcome(props) {
return html`<h1>Hello, ${props.name}</h1>`;
}
render(
Welcome({name: 'Elliott'}),
document.body.querySelector('#root')
);
6. Stan i cykl życia
W tej sekcji dowiesz się więcej o stanie i cyklu życia komponentu Lit.
Stan
Koncepcja „właściwości reaktywnych” w Lit to połączenie stanu i właściwości Reacta. Właściwości reaktywne po zmianie mogą wywoływać cykl życia komponentu. Właściwości reaktywne występują w 2 wariantach:
Publiczne właściwości reaktywne
// 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';
}
- Zdefiniowane przez
@property - Podobne do właściwości i stanu w React, ale modyfikowalne
- Publiczny interfejs API, do którego dostęp mają użytkownicy komponentu i który jest przez nich ustawiany.
Wewnętrzny stan reaktywny
// 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';
}
- Zdefiniowane przez
@state - Podobny do stanu w React, ale modyfikowalny
- Prywatny stan wewnętrzny, do którego zwykle uzyskuje się dostęp z poziomu komponentu lub podklas.
Lifecycle
Cykl życia Lit jest dość podobny do cyklu życia React, ale występują między nimi pewne istotne różnice.
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';
}
}
- Odpowiednik w litrach to również
constructor. - Nie musisz niczego przekazywać do superwywołania.
- Wywoływane przez (niepełna lista):
document.createElementdocument.innerHTMLnew ComponentClass()- Jeśli na stronie znajduje się nazwa nieuaktualnionego tagu, a definicja jest wczytana i zarejestrowana za pomocą funkcji
@customElementlubcustomElements.define.
- Podobny w działaniu do funkcji
constructorw React
render
// React
render() {
return <div>Hello World</div>
}
// Lit
render() {
return html`<div>Hello World</div>`;
}
- Odpowiednik w litrach to również
render. - Może zwracać dowolny wynik renderowania, np.
TemplateResultlubstring. - Podobnie jak w przypadku Reacta,
render()powinna być funkcją czystą. - Renderowanie w węźle, który zwraca
createRenderRoot()(domyślnieShadowRoot).
componentDidMount
componentDidMount jest podobne do połączenia wywołań zwrotnych cyklu życia firstUpdated i connectedCallback w bibliotece Lit.
firstUpdated
import Chart from 'chart.js';
// React
componentDidMount() {
this._chart = new Chart(this.chartElRef.current, {...});
}
// Lit
firstUpdated() {
this._chart = new Chart(this.chartEl, {...});
}
- Wywoływana po raz pierwszy, gdy szablon komponentu jest renderowany w jego katalogu głównym.
- Będzie wywoływana tylko wtedy, gdy element jest połączony, np.nie będzie wywoływana przez
document.createElement('my-component'), dopóki węzeł nie zostanie dołączony do drzewa DOM. - To dobre miejsce na skonfigurowanie komponentu, które wymaga renderowania DOM przez komponent.
- W przeciwieństwie do Reacta
componentDidMountzmiany właściwości reaktywnych wfirstUpdatedpowodują ponowne renderowanie, chociaż przeglądarka zwykle łączy zmiany w tej samej ramce. Jeśli te zmiany nie wymagają dostępu do DOM roota, zwykle powinny być umieszczane wwillUpdate
connectedCallback
// React
componentDidMount() {
this.window.addEventListener('resize', this.boundOnResize);
}
// Lit
connectedCallback() {
super.connectedCallback();
this.window.addEventListener('resize', this.boundOnResize);
}
- Wywoływana za każdym razem, gdy element niestandardowy jest wstawiany do drzewa DOM.
- W przeciwieństwie do komponentów React, gdy elementy niestandardowe są odłączane od DOM, nie są niszczone, więc można je „łączyć” wielokrotnie.
firstUpdatednie zostanie ponownie wywołana.
- Przydatne do ponownego inicjowania DOM lub ponownego dołączania detektorów zdarzeń, które zostały wyczyszczone po odłączeniu.
- Uwaga: funkcja
connectedCallbackmoże być wywoływana przed funkcjąfirstUpdated, więc podczas pierwszego wywołania DOM może być niedostępny.
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);
}
}
- Odpowiednik w języku litewskim to
updated(w czasie przeszłym w języku angielskim). - W przeciwieństwie do Reacta funkcja
updatedjest wywoływana również podczas początkowego renderowania. - Podobny w działaniu do funkcji
componentDidUpdatew React
componentWillUnmount
// React
componentWillUnmount() {
this.window.removeEventListener('resize', this.boundOnResize);
}
// Lit
disconnectedCallback() {
super.disconnectedCallback();
this.window.removeEventListener('resize', this.boundOnResize);
}
- Lit equivalent jest podobny do
disconnectedCallback - W przeciwieństwie do komponentów React, gdy elementy niestandardowe są odłączone od DOM, komponent nie jest niszczony.
- W przeciwieństwie do
componentWillUnmount,disconnectedCallbackjest wywoływane po usunięciu elementu z drzewa. - DOM wewnątrz elementu głównego jest nadal dołączony do poddrzewa elementu głównego.
- Przydatne do usuwania detektorów zdarzeń i odwołań powodujących wycieki pamięci, aby przeglądarka mogła przeprowadzić odśmiecanie komponentu.
Ćwiczenia
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')
);
W powyższym przykładzie mamy prosty zegar, który wykonuje te czynności:
- Wyświetla „Hello World! Jest" i wyświetla godzinę.
- Co sekundę będzie aktualizować zegar
- Po odłączeniu usuwa interwał wywołujący funkcję tick.
Najpierw zadeklaruj klasę komponentu:
// 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);
Następnie zainicjuj zmienną date i zadeklaruj ją jako wewnętrzną właściwość reaktywną za pomocą @state, ponieważ użytkownicy komponentu nie będą ustawiać zmiennej date bezpośrednio.
// 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);
Następnie wyrenderuj szablon.
// Lit (JS & TS)
render() {
return html`
<div>
<h1>Hello, World!</h1>
<h2>It is ${this.date.toLocaleTimeString()}.</h2>
</div>
`;
}
Teraz zastosuj metodę zaznaczania.
tick() {
this.date = new Date();
}
Następnie należy wdrożyć componentDidMount. Analog Lit to mieszanina firstUpdated i connectedCallback. W przypadku tego komponentu wywołanie tick z parametrem setInterval nie wymaga dostępu do DOM w elemencie głównym. Dodatkowo interwał zostanie wyczyszczony, gdy element zostanie usunięty z drzewa dokumentu, więc jeśli zostanie ponownie dołączony, interwał będzie musiał zacząć się od nowa. Dlatego w tym przypadku lepszym wyborem jest connectedCallback.
// 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
);
}
Na koniec wyczyść interwał, aby nie wykonywał funkcji po odłączeniu elementu od drzewa dokumentu.
// Lit (TS & JS)
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
Po połączeniu wszystkich elementów powinno to wyglądać tak:
// 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. Elementy przykuwające uwagę
W tej sekcji dowiesz się, jak przełożyć koncepcje React Hook na Lit.
Pojęcia dotyczące hooków Reacta
Haczyki React umożliwiają komponentom funkcyjnym „podłączanie się” do stanu. Ma to kilka zalet:
- Ułatwiają ponowne używanie logiki stanowej.
- Pomoc w podzieleniu komponentu na mniejsze funkcje
Dodatkowo skupienie się na komponentach opartych na funkcjach rozwiązało pewne problemy z syntaksą opartą na klasach w React, takie jak:
- Musisz przejść z
propszconstructordosuper - Niechlujna inicjalizacja właściwości w
constructor- Był to powód podany przez zespół React w tamtym czasie, ale został rozwiązany przez ES2019.
- Problemy spowodowane tym, że
thisnie odnosi się już do komponentu
Koncepcje dotyczące funkcji React Hooks w Lit
Jak wspomnieliśmy w sekcji Komponenty i właściwości, Lit nie umożliwia tworzenia elementów niestandardowych z funkcji, ale LitElement rozwiązuje większość głównych problemów z komponentami klasowymi React. Na przykład:
// 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++;
}
}
Jak Lit rozwiązuje te problemy?
constructornie przyjmuje argumentów- Wszystkie powiązania
@eventsą automatycznie powiązane zthis. thisw zdecydowanej większości przypadków odnosi się do odwołania do elementu niestandardowego.- Właściwości klasy można teraz tworzyć jako elementy klasy. Upraszcza to implementacje oparte na konstruktorach.
Kontrolery reaktywne
Główne koncepcje związane z funkcjami Hooks w Lit są realizowane przez kontrolery reaktywne. Wzorce kontrolera reaktywnego umożliwiają udostępnianie logiki stanowej, dzielenie komponentów na mniejsze, bardziej modułowe części, a także podłączanie się do cyklu życia aktualizacji elementu.
Kontroler reaktywny to interfejs obiektu, który można podłączyć do cyklu aktualizacji hosta kontrolera, takiego jak LitElement.
Cykl życia ReactiveController i reactiveControllerHost wygląda tak:
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>;
}
Tworząc kontroler reaktywny i dołączając go do hosta za pomocą addController, cykl życia kontrolera będzie wywoływany równolegle z cyklem życia hosta. Przypomnij sobie na przykład zegar z sekcji Stan i cykl życia:
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')
);
W powyższym przykładzie znajduje się prosty zegar, który wykonuje te czynności:
- Wyświetla „Hello World! Jest" i wyświetla godzinę.
- Co sekundę będzie aktualizować zegar
- Po odłączeniu usuwa interwał wywołujący funkcję tick.
Tworzenie szkieletu komponentu
Najpierw zacznij od deklaracji klasy komponentu i dodaj funkcję render.
// 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);
Tworzenie kontrolera
Teraz przełącz się na clock.ts i utwórz zajęcia dla ClockController oraz skonfiguruj constructor:
// 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() {
}
}
Kontroler reaktywny można utworzyć w dowolny sposób, o ile udostępnia interfejs ReactiveController, ale w większości podstawowych przypadków zespół Lit woli używać klasy z właściwością constructor, która może przyjmować interfejs ReactiveControllerHost, a także inne właściwości potrzebne do zainicjowania kontrolera.
Teraz musisz przetłumaczyć wywołania zwrotne cyklu życia React na wywołania zwrotne kontrolera. W skrócie:
componentDidMount- do
connectedCallbackLitElement - Do kontrolera
hostConnected
- do
ComponentWillUnmount- do
disconnectedCallbackLitElement - Do kontrolera
hostDisconnected
- do
Więcej informacji o przekładaniu cyklu życia React na cykl życia Lit znajdziesz w sekcji Stan i cykl życia.
Następnie zaimplementuj wywołanie zwrotne hostConnected i metody tick oraz wyczyść interwał w hostDisconnected, tak jak w przykładzie w sekcji Stan i cykl życia.
// 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);
}
}
Korzystanie z kontrolera
Aby użyć kontrolera zegara, zaimportuj go i zaktualizuj komponent w index.ts lub 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);
Aby użyć kontrolera, musisz go utworzyć, przekazując odwołanie do hosta kontrolera (czyli komponentu <my-element>), a następnie użyć kontrolera w metodzie render.
Wywoływanie ponownego renderowania w kontrolerze
Zwróć uwagę, że wyświetla godzinę, ale nie jest ona aktualizowana. Dzieje się tak, ponieważ kontroler ustawia datę co sekundę, ale host jej nie aktualizuje. Dzieje się tak, ponieważ date zmienia się w klasie ClockController, a nie w komponencie. Oznacza to, że po ustawieniu wartości date na kontrolerze host musi zostać poinformowany o konieczności uruchomienia cyklu aktualizacji z wartością host.requestUpdate().
// Lit (TS & JS) - clock.ts / clock.js
private tick() {
this.date = new Date();
this.host.requestUpdate();
}
Zegar powinien teraz tykać.
Bardziej szczegółowe porównanie typowych przypadków użycia z hakami znajdziesz w sekcji Tematy zaawansowane – haki.
8. Dzieci
W tej sekcji dowiesz się, jak używać slotów do zarządzania elementami podrzędnymi w Lit.
Gniazda i dzieci
Sloty umożliwiają tworzenie kompozycji przez zagnieżdżanie komponentów.
W React komponenty podrzędne są dziedziczone przez właściwości. Domyślny slot to props.children, a funkcja render określa jego położenie. Na przykład:
const MyArticle = (props) => {
return <article>{props.children}</article>;
};
Pamiętaj, że props.children to komponenty React, a nie elementy HTML.
W Lit komponenty podrzędne są umieszczane w funkcji renderowania za pomocą elementów slot. Zauważ, że elementy podrzędne nie są dziedziczone w taki sam sposób jak w przypadku React. W Lit dzieci są elementami HTML dołączonymi do slotów. Ten załącznik nazywa się Prognoza.
@customElement("my-article")
export class MyArticle extends LitElement {
render() {
return html`
<article>
<slot></slot>
</article>
`;
}
}
Wiele przedziałów
W React dodawanie wielu slotów jest w zasadzie tym samym co dziedziczenie większej liczby właściwości.
const MyArticle = (props) => {
return (
<article>
<header>
{props.headerChildren}
</header>
<section>
{props.sectionChildren}
</section>
</article>
);
};
Podobnie dodanie większej liczby elementów <slot> tworzy więcej miejsc w Lit. Zdefiniowano wiele miejsc docelowych z atrybutem name: <slot name="slot-name">. Dzięki temu dzieci mogą zadeklarować, które miejsce zostanie im przypisane.
@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>
`;
}
}
Treści domyślne w przestrzeni reklamowej
Gniazda będą wyświetlać swoje poddrzewo, gdy nie będą do nich kierowane żadne węzły. Gdy węzły są rzutowane na gniazdo, gniazdo nie wyświetla poddrzewa, tylko rzutowane węzły.
@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>
`;
}
}
Przypisywanie dzieci do miejsc
W React komponenty podrzędne są przypisywane do miejsc za pomocą właściwości komponentu. W przykładzie poniżej elementy React są przekazywane do właściwości headerChildren i sectionChildren.
const MyNewsArticle = () => {
return (
<MyArticle
headerChildren={<h3>Extry, Extry! Read all about it!</h3>}
sectionChildren={<p>Children are props in React!</p>}
/>
);
};
W Lit dzieci są przypisywane do slotów za pomocą atrybutu slot.
@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>
`;
}
}
Jeśli nie ma domyślnego gniazda (np. <slot>) ani gniazda z atrybutem name (np. <slot name="foo">), który pasuje do atrybutu slot elementów podrzędnych elementu niestandardowego (np. <div slot="foo">), węzeł nie zostanie wyświetlony.
9. Refs
Czasami deweloper może potrzebować dostępu do interfejsu API elementu HTMLElement.
W tej sekcji dowiesz się, jak uzyskać odwołania do elementów w Lit.
Odwołania w React
Komponent React jest transpilowany do serii wywołań funkcji, które po wywołaniu tworzą wirtualny DOM. Wirtualny DOM jest interpretowany przez ReactDOM i renderuje elementy HTML.
W React odwołania to miejsce w pamięci, w którym znajduje się wygenerowany element HTMLElement.
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>
);
};
W przykładzie powyżej komponent React wykona te czynności:
- Wyświetl puste pole tekstowe i przycisk z tekstem
- Ustawianie fokusu na polu wprowadzania po kliknięciu przycisku
Po początkowym renderowaniu React ustawi wartość inputRef.current na wygenerowaną wartość HTMLInputElement za pomocą atrybutu ref.
Podświetl „References” za pomocą @query
Lit działa blisko przeglądarki i tworzy bardzo cienką warstwę abstrakcji nad natywnymi funkcjami przeglądarki.
Odpowiednikiem elementu refs w Lit jest element HTMLElement zwracany przez dekoratory @query i @queryAll.
@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>
`;
}
}
W przykładzie powyżej komponent Lit wykonuje te czynności:
- Definiuje właściwość w
MyElementza pomocą dekoratora@query(tworzy funkcję pobierającą dlaHTMLInputElement). - Deklaruje i dołącza wywołanie zwrotne zdarzenia kliknięcia o nazwie
onButtonClick. - Ustawia fokus na polu wejściowym po kliknięciu przycisku.
W JavaScript dekoratory @query i @queryAll wykonują odpowiednio operacje querySelector i querySelectorAll. Jest to odpowiednik JavaScriptu dla @query('input') inputEl!: HTMLInputElement;
get inputEl() {
return this.renderRoot.querySelector('input');
}
Gdy komponent Lit zatwierdzi szablon metody render w korzeniu my-element, dekorator @query umożliwi inputEl zwrócenie pierwszego elementu input znalezionego w korzeniu renderowania. Jeśli @query nie może znaleźć określonego elementu, zwraca wartość null.
Jeśli w korzeniu renderowania było kilka elementów input, funkcja @queryAll zwróci listę węzłów.
10. Stan pośredniczenia
W tej sekcji dowiesz się, jak zarządzać stanem między komponentami w Lit.
Komponenty wielokrotnego użytku
React naśladuje funkcjonalne potoki renderowania z przepływem danych od góry do dołu. Rodzice przekazują dzieciom stan za pomocą rekwizytów. Komponenty podrzędne komunikują się z komponentami nadrzędnymi za pomocą wywołań zwrotnych znajdujących się w propsach.
const CounterButton = (props) => {
const label = props.step < 0
? `- ${-1 * props.step}`
: `+ ${props.step}`;
return (
<button
onClick={() =>
props.addToCounter(props.step)}>{label}</button>
);
};
W przykładzie powyżej komponent React wykonuje te czynności:
- Tworzy etykietę na podstawie wartości
props.step. - Renderuje przycisk z etykietą +step lub -step.
- Aktualizuje komponent nadrzędny, wywołując funkcję
props.addToCounterz argumentemprops.steppo kliknięciu.
Chociaż w Lit można przekazywać wywołania zwrotne, konwencjonalne wzorce są inne. Komponent React z przykładu powyżej można zapisać jako komponent Lit w przykładzie poniżej:
@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>
`;
}
}
W przykładzie powyżej komponent Lit wykona te czynności:
- Utwórz właściwość reaktywną
step - Wysyłanie zdarzenia niestandardowego o nazwie
update-counter, które po kliknięciu zawiera wartość elementustep
Zdarzenia przeglądarki są przekazywane z elementów podrzędnych do elementów nadrzędnych. Zdarzenia umożliwiają dzieciom transmitowanie zdarzeń interakcji i zmian stanu. React zasadniczo przekazuje stan w przeciwnym kierunku, więc rzadko zdarza się, aby komponenty React wysyłały i nasłuchiwały zdarzeń w taki sam sposób jak komponenty Lit.
Komponenty stanowe
W React często używa się hooków do zarządzania stanem. MyCounter Komponent można utworzyć, ponownie wykorzystując CounterButton komponent. Zwróć uwagę, jak element addToCounter jest przekazywany do obu instancji elementu CounterButton.
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>
);
};
Powyższy przykład wykonuje te czynności:
- Tworzy stan
count. - Tworzy wywołanie zwrotne, które dodaje liczbę do
countstanu. CounterButtonużywaaddToCounterdo aktualizowaniacountostepprzy każdym kliknięciu.
Podobną implementację MyCounter można uzyskać w Lit. Zwróć uwagę, że wartość addToCounter nie jest przekazywana do funkcji counter-button. Zamiast tego wywołanie zwrotne jest powiązane jako detektor zdarzeń ze zdarzeniem @update-counter na elemencie nadrzędnym.
@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>
`;
}
}
Powyższy przykład wykonuje te czynności:
- Tworzy właściwość reaktywną o nazwie
count, która będzie aktualizować komponent, gdy zmieni się jej wartość. - Wiąże wywołanie zwrotne
addToCounterz detektorem zdarzeń@update-counter. - Aktualizuje
count, dodając wartość znalezioną wdetail.stepzdarzeniaupdate-counter. - Ustawia wartość
stepelementucounter-buttonza pomocą atrybutustep.
W Lit bardziej konwencjonalne jest używanie właściwości reaktywnych do przekazywania zmian z elementów nadrzędnych do podrzędnych. Podobnie warto używać systemu zdarzeń przeglądarki, aby przekazywać szczegóły od dołu do góry.
To podejście jest zgodne ze sprawdzonymi metodami i z celem biblioteki Lit, jakim jest zapewnienie obsługi komponentów internetowych na wielu platformach.
11. Styl
W tej sekcji dowiesz się więcej o stylach w Lit.
Styl
Lit oferuje wiele sposobów na stylizowanie elementów, a także wbudowane rozwiązanie.
Style wbudowane
Lit obsługuje style wbudowane, a także powiązanie z nimi.
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>
`;
}
}
W powyższym przykładzie znajdują się 2 nagłówki, z których każdy ma styl wbudowany.
Teraz zaimportuj i powiąż obramowanie z grupy border-color.js z pomarańczowym tekstem:
...
import borderColor from './border-color.js';
...
html`
...
<h1 style="color:orange;${borderColor}">This text is orange</h1>
...`
Obliczanie ciągu stylów za każdym razem może być nieco uciążliwe, dlatego Lit oferuje dyrektywę, która w tym pomaga.
styleMap
styleMap Dyrektywa ułatwia używanie JavaScriptu do ustawiania stylów wbudowanych. Na przykład:
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>
`;
}
}
Powyższy przykład wykonuje te czynności:
- Wyświetla ikonę
h1z obramowaniem i selektorem kolorów. - Zmienia
border-colorna wartość z selektora kolorów.
Dodatkowo istnieje element styleMap, który służy do ustawiania stylów elementu h1. styleMap ma składnię podobną do składni wiązania atrybutów style w React.
CSSResult
Zalecany sposób stylizowania komponentów to użycie literału szablonu oznaczonego tagiem css.
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>
`;
}
}
Powyższy przykład wykonuje te czynności:
- Deklaruje literał szablonu z tagami CSS z powiązaniem.
- Ustawia kolory 2
h1o identyfikatorach
Zalety używania tagu szablonu css:
- Analizowanie raz na klasę a raz na instancję
- Wdrożone z myślą o możliwości ponownego wykorzystania modułów
- Możesz łatwo rozdzielić style na osobne pliki.
- Zgodność z kodem polyfill niestandardowych właściwości CSS
Zwróć też uwagę na tag <style> w index.html:
<!-- index.html -->
<style>
h1 {
color: red !important;
}
</style>
Lit ograniczy style komponentów do ich elementów głównych. Oznacza to, że style nie będą się przenikać. Aby przekazywać style do komponentów, zespół Lit zaleca używanie niestandardowych właściwości CSS, ponieważ mogą one przenikać zakres stylów Lit.
Tagi stylu
Możesz też po prostu wstawić tagi <style> w szablonach. Przeglądarka usunie duplikaty tych tagów stylu, ale umieszczenie ich w szablonach spowoduje, że będą one analizowane w przypadku każdej instancji komponentu, a nie każdej klasy, jak w przypadku szablonu oznaczonego tagiem css. Dodatkowo deduplikacja CSSResult w przeglądarce jest znacznie szybsza.
Tagi linków
W szablonie możesz też użyć elementu <link rel="stylesheet">, ale nie jest to zalecane, ponieważ może powodować początkowy błysk niestylizowanych treści (FOUC).
12. Tematy zaawansowane (opcjonalnie)
JSX i szablony
Lit i wirtualny DOM
Lit-html nie zawiera konwencjonalnego wirtualnego DOM, który porównuje każdy węzeł. Zamiast tego wykorzystuje funkcje wydajnościowe wbudowane w specyfikację oznaczonych literałów szablonu ES2015. Oznaczone literały szablonu to ciągi literałów szablonu z dołączonymi do nich funkcjami tagów.
Oto przykład literału szablonu:
const str = 'string';
console.log(`This is a template literal ${str}`);
Oto przykład literału szablonu z tagiem:
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
W powyższym przykładzie tagiem jest funkcja tag, a funkcja f zwraca wywołanie oznaczonego szablonu literału.
Wiele funkcji Lit związanych z wydajnością wynika z faktu, że tablice ciągów znaków przekazywane do funkcji tagu mają ten sam wskaźnik (jak pokazano w drugim console.log). Przeglądarka nie tworzy nowej tablicy strings przy każdym wywołaniu funkcji tagu, ponieważ używa tego samego literału szablonu (czyli w tym samym miejscu w AST). Dzięki temu wiązanie, parsowanie i pamięć podręczna szablonów w Lit mogą korzystać z tych funkcji bez dużego obciążenia związanego z różnicami w czasie działania.
To wbudowane w przeglądarkę działanie oznaczonych literałów szablonu daje bibliotece Lit znaczną przewagę pod względem wydajności. Większość tradycyjnych wirtualnych DOM-ów wykonuje większość pracy w JavaScript. Jednak w przypadku oznaczonych literałów szablonu większość porównań różnicowych jest wykonywana w C++ przeglądarki.
Jeśli chcesz zacząć używać literałów szablonu z tagami HTML w React lub Preact, zespół Lit zaleca bibliotekę htm.
Jednak, jak w przypadku witryny Google Codelabs i kilku edytorów kodu online, zauważysz, że podświetlanie składni oznaczonych literałów szablonu nie jest zbyt powszechne. Niektóre środowiska IDE i edytory tekstu obsługują je domyślnie, np. Atom i wyróżnianie bloków kodu w GitHubie. Zespół Lit ściśle współpracuje ze społecznością, aby utrzymywać projekty takie jak lit-plugin, czyli wtyczka do VS Code, która dodaje do projektów Lit wyróżnianie składni, sprawdzanie typów i funkcję IntelliSense.
Lit & JSX + React DOM
JSX nie działa w przeglądarce, ale używa preprocesora do konwertowania JSX na wywołania funkcji JavaScript (zwykle za pomocą Babel).
Na przykład Babel przekształci ten kod:
const element = <div className="title">Hello World!</div>;
ReactDOM.render(element, mountNode);
w ten sposób:
const element = React.createElement('div', {className: 'title'}, 'Hello World!');
ReactDOM.render(element, mountNode);
React DOM pobiera dane wyjściowe Reacta i przekształca je w rzeczywisty DOM – właściwości, atrybuty, detektory zdarzeń i wszystko inne.
Lit-html używa literałów szablonu z tagami, które można uruchamiać w przeglądarce bez transpilacji ani preprocesora. Oznacza to, że aby zacząć korzystać z Lit, wystarczy plik HTML, skrypt modułu ES i serwer. Oto skrypt, który można w całości uruchomić w przeglądarce:
<!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>
Dodatkowo system szablonów Lit, lit-html, nie używa konwencjonalnego wirtualnego DOM, ale bezpośrednio korzysta z interfejsu DOM API. Rozmiar Lit 2 po zminimalizowaniu i skompresowaniu wynosi poniżej 5 KB, a w przypadku React (2,8 KB) i react-dom (39,4 KB) jest to 40 KB.
Wydarzenia
React używa systemu zdarzeń syntetycznych. Oznacza to, że react-dom musi zdefiniować każde zdarzenie, które będzie używane w każdym komponencie, i zapewnić odpowiednik w formie detektora zdarzeń w notacji camelCase dla każdego typu węzła. W rezultacie JSX nie ma metody definiowania detektora zdarzeń niestandardowych, więc programiści muszą używać ref, a potem imperatywnie stosować detektor. W przypadku integracji bibliotek, które nie są przeznaczone do Reacta, powoduje to gorsze wrażenia programisty, co zmusza go do pisania specjalnego modułu opakowującego dla Reacta.
Lit-html ma bezpośredni dostęp do DOM i używa natywnych zdarzeń, więc dodawanie detektorów zdarzeń jest tak proste jak @event-name=${eventNameListener}. Oznacza to, że podczas dodawania odbiorników zdarzeń i wywoływania zdarzeń wykonywane jest mniej analizowania w czasie działania.
Komponenty i rekwizyty
Komponenty React i elementy niestandardowe
LitElement używa elementów niestandardowych do pakowania komponentów. Elementy niestandardowe wprowadzają pewne kompromisy między komponentami React, jeśli chodzi o komponentyzację (stan i cykl życia są omówione w sekcji Stan i cykl życia).
Oto niektóre zalety elementów niestandardowych jako systemu komponentów:
- Są one natywne dla przeglądarki i nie wymagają żadnych narzędzi.
- Dopasowanie do każdego interfejsu API przeglądarki, od
innerHTMLidocument.createElementpoquerySelector - Zwykle można go używać w różnych platformach.
- Może być zarejestrowany z użyciem leniwego ładowania za pomocą
customElements.definei „hydratyzować” DOM.
Oto niektóre wady elementów niestandardowych w porównaniu z komponentami React:
- Nie można utworzyć elementu niestandardowego bez zdefiniowania klasy (a tym samym komponentów funkcyjnych podobnych do JSX).
- Musi zawierać tag zamykający
- Uwaga: mimo że jest to wygodne dla deweloperów, dostawcy przeglądarek zwykle żałują specyfikacji tagów zamykających się samodzielnie, dlatego nowsze specyfikacje zwykle ich nie zawierają.
- Wprowadza dodatkowy węzeł do drzewa DOM, co może powodować problemy z układem.
- Musi być zarejestrowany za pomocą JavaScriptu.
Zespół Lit zdecydował się na elementy niestandardowe zamiast na system elementów dostosowanych do potrzeb, ponieważ elementy niestandardowe są wbudowane w przeglądarkę. Zespół Lit uważa, że korzyści wynikające z używania elementów niestandardowych w różnych frameworkach przeważają nad korzyściami wynikającymi z warstwy abstrakcji komponentów. W rzeczywistości zespół Lit w ramach prac nad lit-ssr rozwiązał główne problemy z rejestracją w JavaScript. Niektóre firmy, takie jak GitHub, korzystają też z leniwej rejestracji elementów niestandardowych, aby stopniowo ulepszać strony o opcjonalne funkcje.
Przekazywanie danych do elementów niestandardowych
Częstym nieporozumieniem dotyczącym elementów niestandardowych jest to, że dane można przekazywać tylko jako ciągi znaków. To błędne przekonanie wynika prawdopodobnie z faktu, że atrybuty elementów można zapisywać tylko jako ciągi znaków. Chociaż Lit przekształca atrybuty ciągu znaków na zdefiniowane typy, elementy niestandardowe mogą też akceptować złożone dane jako właściwości.
Na przykład przy tej definicji elementu LitElement:
// 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>`;
}
}
Zdefiniowana jest podstawowa właściwość reaktywna num, która przekształca wartość tekstową atrybutu w number. Następnie wprowadzana jest złożona struktura danych z attribute:false, która dezaktywuje obsługę atrybutów w Lit.
Aby przekazać dane do tego elementu niestandardowego:
<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>
Stan i cykl życia
Inne wywołania zwrotne cyklu życia React
static getDerivedStateFromProps
W Lit nie ma odpowiednika, ponieważ właściwości i stan są takimi samymi właściwościami klasy.
shouldComponentUpdate
- Równowartość w litrach:
shouldUpdate - Wywoływany przy pierwszym renderowaniu, w przeciwieństwie do Reacta
- Podobny w działaniu do funkcji
shouldComponentUpdatew React
getSnapshotBeforeUpdate
W przypadku Lit getSnapshotBeforeUpdate jest podobne zarówno do update, jak i willUpdate
willUpdate
- Połączenie zakończone przed
update - W przeciwieństwie do funkcji
getSnapshotBeforeUpdatefunkcjawillUpdatejest wywoływana przed funkcjąrender. - Zmiany właściwości reaktywnych w
willUpdatenie powodują ponownego uruchomienia cyklu aktualizacji. - Dobre miejsce do obliczania wartości właściwości, które zależą od innych właściwości i są używane w pozostałej części procesu aktualizacji.
- Ta metoda jest wywoływana na serwerze w przypadku renderowania po stronie serwera, więc nie zaleca się tu dostępu do DOM.
update
- Połączenie wykonane po
willUpdate - W przeciwieństwie do funkcji
getSnapshotBeforeUpdatefunkcjaupdatejest wywoływana przed funkcjąrender. - Zmiany właściwości reaktywnych w
updatenie powodują ponownego uruchomienia cyklu aktualizacji, jeśli zostały wprowadzone przed wywołaniemsuper.update. - Dobre miejsce do przechwytywania informacji z interfejsu DOM otaczających komponent przed zatwierdzeniem wyrenderowanych danych wyjściowych w interfejsie DOM.
- Ta metoda nie jest wywoływana na serwerze w przypadku renderowania po stronie serwera.
Inne wywołania zwrotne cyklu życia Lit
Istnieje kilka wywołań zwrotnych cyklu życia, o których nie wspomnieliśmy w poprzedniej sekcji, ponieważ nie mają one odpowiedników w React. Są to:
attributeChangedCallback
Jest wywoływana, gdy zmieni się jeden z elementów observedAttributes. Zarówno observedAttributes, jak i attributeChangedCallback są częścią specyfikacji elementów niestandardowych i są zaimplementowane przez Lit, aby zapewnić interfejs API atrybutów dla elementów Lit.
adoptedCallback
Wywoływana, gdy komponent zostanie przeniesiony do nowego dokumentu, np. z HTMLTemplateElementdocumentFragment do głównego document. Ta funkcja zwrotna jest też częścią specyfikacji elementów niestandardowych i powinna być używana tylko w zaawansowanych przypadkach, gdy komponent zmienia dokumenty.
Inne metody i właściwości związane z cyklem życia
Te metody i właściwości są elementami klasy, które możesz wywoływać, zastępować lub na które możesz czekać, aby manipulować procesem cyklu życia.
updateComplete
Jest to Promise, która jest rozwiązywana, gdy element zakończy aktualizację, ponieważ cykle życia aktualizacji i renderowania są asynchroniczne. Przykład:
async nextButtonClicked() {
this.step++;
// Wait for the next "step" state to render
await this.updateComplete;
this.dispatchEvent(new Event('step-rendered'));
}
getUpdateComplete
Jest to metoda, którą należy zastąpić, aby dostosować moment, w którym updateComplete zostanie rozwiązany. Jest to powszechne, gdy komponent renderuje komponent podrzędny, a ich cykle renderowania muszą być zsynchronizowane, np.
class MyElement extends LitElement {
...
async getUpdateComplete() {
await super.getUpdateComplete();
await this.myChild.updateComplete;
}
}
performUpdate
Ta metoda wywołuje wywołania zwrotne cyklu życia aktualizacji. Zwykle nie jest to potrzebne, z wyjątkiem rzadkich przypadków, w których aktualizacja musi być przeprowadzona synchronicznie lub w ramach niestandardowego harmonogramu.
hasUpdated
Wartość tej właściwości to true, jeśli komponent został zaktualizowany co najmniej raz.
isConnected
Ta właściwość, która jest częścią specyfikacji elementów niestandardowych, będzie miała wartość true, jeśli element jest obecnie dołączony do głównego drzewa dokumentu.
Wizualizacja cyklu życia aktualizacji lit
Cykl życia aktualizacji składa się z 3 etapów:
- Przed aktualizacją
- Zaktualizuj
- Po aktualizacji
Przed aktualizacją

Po requestUpdate oczekiwana jest zaplanowana aktualizacja.
Zaktualizuj

Po aktualizacji

Elementy przykuwające uwagę
Dlaczego warto używać elementów przyciągających uwagę
W React wprowadzono komponenty funkcyjne, które umożliwiają proste przypadki użycia wymagające stanu. W wielu prostych przypadkach komponenty funkcyjne z hookami są znacznie prostsze i czytelniejsze niż ich odpowiedniki w postaci komponentów klasowych. Jednak w przypadku wprowadzania asynchronicznych aktualizacji stanu oraz przekazywania danych między funkcjami lub efektami wzorzec funkcji zwykle nie wystarcza, a rozwiązanie oparte na klasach, takie jak kontrolery reaktywne, sprawdza się lepiej.
Punkty zaczepienia i kontrolery żądań do interfejsu API
Często pisze się hooka, który wysyła żądanie danych do interfejsu API. Weźmy na przykład ten komponent funkcji React, który wykonuje te czynności:
index.tsx- Renderuje tekst
- Wyświetla odpowiedź
useAPI- Identyfikator użytkownika + nazwa użytkownika
- Komunikat o błędzie
- Błąd 404 po dotarciu do 11 użytkownika (zgodnie z założeniami)
- Błąd przerwania, jeśli pobieranie interfejsu API zostanie przerwane
- Wczytywanie wiadomości
- Renderuje przycisk polecenia.
- Następny użytkownik: pobiera interfejs API dla następnego użytkownika.
- Anuluj: przerywa pobieranie danych z interfejsu API i wyświetla błąd.
useApi.tsx- Definiuje
useApiniestandardowy hook - Asynchroniczne pobieranie obiektu użytkownika z interfejsu API
- Wysyła:
- Nazwa użytkownika
- Czy pobieranie jest w toku
- wszelkie komunikaty o błędach;
- Wywołanie zwrotne, które przerywa pobieranie.
- Przerywa trwające pobieranie danych, jeśli komponent zostanie odmontowany.
- Definiuje
Oto implementacja kontrolera Lit + Reactive.
Wnioski:
- Kontrolery reaktywne są najbardziej podobne do niestandardowych hooków.
- Przekazywanie danych, które nie podlegają renderowaniu, między wywołaniami zwrotnymi a efektami
- React używa
useRefdo przekazywania danych międzyuseEffectauseCallback. - Lit używa prywatnej właściwości klasy
- React w zasadzie naśladuje zachowanie prywatnej właściwości klasy.
- React używa
Jeśli podoba Ci się składnia komponentów funkcyjnych React z hookami, ale chcesz korzystać z tego samego środowiska bez kompilacji co w przypadku Lit, zespół Lit gorąco poleca bibliotekę Haunted.
Dzieci
Domyślny slot
Jeśli elementy HTML nie mają atrybutu slot, są przypisywane do domyślnego nienazwanego przedziału. W przykładzie poniżej funkcja MyApp umieści jeden akapit w nazwanym slocie. Drugi akapit zostanie domyślnie umieszczony w nieokreślonym slocie.
@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>
`;
}
}
Aktualizacje przedziałów
Gdy zmieni się struktura elementów podrzędnych slotu, zostanie wywołane zdarzenie slotchange. Komponent Lit może powiązać detektor zdarzeń ze zdarzeniem slotchange. W przykładzie poniżej w przypadku pierwszego gniazda znalezionego w shadowRoot wartość assignedNodes zostanie zarejestrowana w konsoli w slotchange.
@customElement("my-element")
export class MyElement extends LitElement {
onSlotChange(e: Event) {
const slot = this.shadowRoot.querySelector('slot');
console.log(slot.assignedNodes({flatten: true}));
}
render() {
return html`
<section>
<div>
<slot @slotchange="{this.onSlotChange}"></slot>
</div>
</section>
`;
}
}
Refs
Generowanie odniesień
Zarówno Lit, jak i React udostępniają odwołanie do elementu HTMLElement po wywołaniu funkcji render. Warto jednak sprawdzić, jak React i Lit tworzą DOM, który jest później zwracany przez dekorator Lit @query lub odwołanie React.
React to funkcjonalny potok, który tworzy komponenty React, a nie elementy HTML. Ponieważ obiekt Ref jest deklarowany przed wyrenderowaniem elementu HTMLElement, w pamięci jest przydzielane miejsce. Dlatego początkową wartością elementu Ref jest null, ponieważ rzeczywisty element DOM nie został jeszcze utworzony (ani wyrenderowany), czyli useRef(null).
Gdy ReactDOM przekształci komponent Reacta w HTMLElement, wyszuka w nim atrybut o nazwie ref. Jeśli jest dostępny, ReactDOM umieszcza odwołanie do HTMLElement w ref.current.
LitElement używa funkcji tagu szablonu html z lit-html do tworzenia elementu szablonu. Po renderowaniu LitElement umieszcza zawartość szablonu w modelu Shadow DOM elementu niestandardowego. Shadow DOM to drzewo DOM o określonym zakresie, hermetyzowane przez element shadow root. Dekorator @query tworzy następnie getter właściwości, który zasadniczo wykonuje operację this.shadowRoot.querySelector na zakorzenionym elemencie w zakresie.
Wysyłanie zapytań do wielu elementów
W poniższym przykładzie dekorator @queryAll zwróci 2 akapi w głównym elemencie cienia jako NodeList.
@customElement("my-element")
export class MyElement extends LitElement {
@queryAll('p')
paragraphs!: NodeList;
render() {
return html`
<p>Hello, world!</p>
<p>How are you?</p>
`;
}
}
W zasadzie @queryAll tworzy funkcję pobierającą dla paragraphs, która zwraca wyniki this.shadowRoot.querySelectorAll(). W JavaScript można zadeklarować funkcję pobierającą, która będzie pełnić tę samą rolę:
get paragraphs() {
return this.renderRoot.querySelectorAll('p');
}
Elementy zmieniające zapytanie
Dekorator @queryAsync lepiej nadaje się do obsługi węzła, który może się zmieniać w zależności od stanu właściwości innego elementu.
W przykładzie poniżej @queryAsync znajdzie pierwszy element akapitu. Element akapitu będzie jednak renderowany tylko wtedy, gdy funkcja renderParagraph wygeneruje losowo liczbę nieparzystą. Dyrektywa @queryAsync zwróci obietnicę, która zostanie spełniona, gdy pierwszy akapit będzie dostępny.
@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()}
`;
}
}
Stan pośredniczenia
W React przyjęło się używać wywołań zwrotnych, ponieważ stan jest zarządzany przez samą bibliotekę React. React stara się nie polegać na stanie dostarczanym przez elementy. DOM jest po prostu efektem procesu renderowania.
Stan zewnętrzny
Możesz używać Redux, MobX lub dowolnej innej biblioteki do zarządzania stanem razem z Lit.
Komponenty Lit są tworzone w zakresie przeglądarki. Dlatego każda biblioteka, która jest też dostępna w zakresie przeglądarki, jest dostępna dla Lit. Wiele wspaniałych bibliotek zostało stworzonych z myślą o wykorzystaniu w Lit istniejących systemów zarządzania stanem.
Oto seria artykułów od Vaadin, w której wyjaśniono, jak wykorzystać Redux w komponencie Lit.
Zapoznaj się z biblioteką lit-mobx od Adobe, aby dowiedzieć się, jak duża witryna może wykorzystać MobX w Lit.
Zapoznaj się też z Apollo Elements, aby dowiedzieć się, jak deweloperzy uwzględniają GraphQL w komponentach internetowych.
Lit współpracuje z natywnymi funkcjami przeglądarki, a większość rozwiązań do zarządzania stanem w zakresie przeglądarki może być używana w komponencie Lit.
Styl
Shadow DOM
Aby natywnie hermetyzować style i DOM w niestandardowym elemencie, Lit używa Shadow DOM. Shadow Roots generuje drzewo cieni oddzielone od głównego drzewa dokumentu. Oznacza to, że większość stylów jest ograniczona do tego dokumentu. Niektóre style, takie jak kolor i inne style związane z czcionką, są przenoszone.
Model Shadow DOM wprowadza też nowe pojęcia i selektory do specyfikacji CSS:
: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.
*/
}
Udostępnianie stylów
Lit ułatwia udostępnianie stylów między komponentami w postaci CSSTemplateResults za pomocą tagów szablonu css. Na przykład:
// 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>`
}
}
Motywy
Shadow roots stanowią pewne wyzwanie dla tradycyjnego motywu, który zwykle jest podejściem do tagów od góry do dołu. Tradycyjnym sposobem na rozwiązanie problemu z motywami w przypadku komponentów internetowych korzystających z Shadow DOM jest udostępnienie interfejsu API stylu za pomocą niestandardowych właściwości CSS. Oto przykład wzorca używanego w Material Design:
.mdc-textfield-outline {
border-color: var(--mdc-theme-primary, /* default value */ #...);
}
.mdc-textfield--input {
caret-color: var(--mdc-theme-primary, #...);
}
Użytkownik może następnie zmienić motyw witryny, stosując wartości właściwości niestandardowych:
html {
--mdc-theme-primary: #F00;
}
html[dark] {
--mdc-theme-primary: #F88;
}
Jeśli musisz zastosować motywowanie odgórne i nie możesz udostępnić stylów, zawsze możesz wyłączyć Shadow DOM, zastępując createRenderRoot wartością this. Spowoduje to renderowanie szablonu komponentów w samym elemencie niestandardowym, a nie w dołączonym do niego katalogu głównego Shadow DOM. W ten sposób utracisz hermetyzację stylu, hermetyzację DOM i sloty.
Produkcja
IE 11
Jeśli musisz obsługiwać starsze przeglądarki, takie jak IE 11, musisz wczytać niektóre polyfille, które zajmują około 33 KB. Więcej informacji znajdziesz tutaj.
Pakiety warunkowe
Zespół Lit zaleca udostępnianie dwóch różnych pakietów: jednego dla IE 11 i jednego dla nowoczesnych przeglądarek. Daje to kilka korzyści:
- Wyświetlanie ES6 jest szybsze i obsługuje większość klientów.
- Przekompilowany kod ES5 znacznie zwiększa rozmiar pakietu.
- Pakiety warunkowe łączą zalety obu rozwiązań
- Obsługa IE 11
- Brak spowolnienia w nowoczesnych przeglądarkach
Więcej informacji o tym, jak utworzyć pakiet wyświetlany warunkowo, znajdziesz w naszej dokumentacji tutaj.