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
- Odsyłacze
- 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ść React
- (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
Pod wieloma względami podstawowe koncepcje i możliwości Lit są 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, lit-html, jest porównywany z wirtualnym DOM Reacta, lit-html jest o 8–10% szybszy od Reacta w najgorszym przypadku i o ponad 50%szybszy w bardziej typowych zastosowaniach.
LitElement (podstawowa klasa bazowa komponentów Lit) dodaje minimalny narzut do lit-html, ale przewyższa wydajność 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 środowiska deweloperskiego wokół natywnych modułów ES było korzystne dla Lit – Lit to po prostu zwykły JavaScript, więc nie ma potrzeby używania interfejsów CLI specyficznych dla frameworka ani obsługi kompilacji.
Niezależne od platformy
Komponenty Lit są oparte na zestawie standardów internetowych o nazwie Web Components. Oznacza to, że komponent utworzony w Lit będzie działać w obecnych i przyszłych frameworkach. 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. Referencje w React nie są jednak wygodne dla programistów.
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 będziesz musiał 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 TypeScriptu
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 czasie 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 przewodnika zespół Lit nie udostępnił jeszcze narzędzi SSR w stabilnej wersji, ale już wdraża komponenty renderowane po stronie serwera w usługach Google i testuje SSR w aplikacjach React. Zespół Lit planuje wkrótce udostępnić te narzędzia w usłudze GitHub.
Postępy zespołu Lit możesz śledzić tutaj.
Wymaga niewielkich nakładów
Korzystanie z Lit nie wymaga dużego zaangażowania. Możesz tworzyć komponenty w Lit i dodawać je do istniejącego projektu. Jeśli Ci się nie spodobają, nie musisz od razu przekształcać całej aplikacji, ponieważ komponenty internetowe działają w innych platformach.
Czy masz już całą aplikację w Lit i chcesz przejść na inną platformę? W takim przypadku możesz umieścić obecną aplikację Lit w nowych ramach i przenieść do komponentów nowych ram 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 Playground, 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ść w środowisku Lit Playground, 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, a także sprawdzić swoją pracę za pomocą tych punktów kontrolnych.
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).
- Przykład:
package.json
- 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ą fragmentu React 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 apostrofu (').
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.
W ramach ćwiczenia 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” (w tym przypadku „false”).
- Ustawia klasę na
static-classplus zmienna (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 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” (prawda), 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żywane jest powiązanie z natywnym input zdarzeniem elementu <input>, ponieważ natywne change zdarzenie jest wywoływane tylko w przypadku elementu 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 w 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"
Tak to wygląda 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 komponentu funkcyjnego, ponieważ nie używa JSX ani preprocesora. Można jednak łatwo utworzyć funkcję, która przyjmuje właściwości i na ich podstawie renderuje DOM. Na przykład:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Elliott"/>
ReactDOM.render(
element,
mountNode
);
W języku litewskim będzie to:
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 są 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.
TemplateResultlubstringitp. - Podobnie jak w przypadku Reacta,
render()powinna być funkcją czystą. - Renderowanie w węźle, który zwróci
createRenderRoot()(domyślnieShadowRoot).
componentDidMount
componentDidMount jest podobny 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 elemencie głównym.
- Funkcja zostanie wywołana tylko wtedy, gdy element jest połączony, np.nie zostanie wywołana za pomocą
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 zmiany we właściwościach reaktywnych w
firstUpdatedpowodują ponowne renderowanie, chociaż przeglądarka zwykle łączy zmiany w tej samej ramce.componentDidMountJeśli te zmiany nie wymagają dostępu do DOM elementu głównego, 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, dlatego 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 przedfirstUpdated, 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 polskim to
updated. - W przeciwieństwie do Reacta funkcja
updatedjest wywoływana również podczas początkowego renderowania. - Podobny do funkcji
componentDidUpdatew React
componentWillUnmount
// React
componentWillUnmount() {
this.window.removeEventListener('resize', this.boundOnResize);
}
// Lit
disconnectedCallback() {
super.disconnectedCallback();
this.window.removeEventListener('resize', this.boundOnResize);
}
- Równowartość w litrach jest podobna do
disconnectedCallback - W przeciwieństwie do komponentów React, gdy elementy niestandardowe są odłączane od DOM, komponent nie jest niszczony.
- W przeciwieństwie do
componentWillUnmount,disconnectedCallbackjest wywoływane po usunięciu elementu z drzewa. - DOM w jednostce głównej jest nadal dołączony do poddrzewa jednostki głównej.
- Przydatne do usuwania detektorów zdarzeń i odwołań powodujących wycieki pamięci, aby przeglądarka mogła zebrać nieużywane elementy.
Ć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 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ł się rozpocząć 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ł działania 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 React
Hooki Reacta 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
constructordosuperprzezprops - Nieuporządkowana inicjalizacja właściwości w
constructor- Był to powód podany przez zespół Reacta, ale został rozwiązany przez ES2019.
- Problemy spowodowane tym, że
thisnie odnosi się już do komponentu
Koncepcje dotyczące hooków React 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 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
Podstawowe koncepcje związane z funkcjami Hooks w Lit są realizowane przez reaktywne kontrolery. 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 aktualizacji elementu.
Kontroler reaktywny to interfejs obiektu, który można podłączyć do cyklu życia 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>;
}
Jeśli utworzysz kontroler reaktywny i dołączysz 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 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 zespół Lit w większości podstawowych przypadków woli używać klasy z interfejsem constructor, który 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 utworzyć jego instancję, 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 się godzina, 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 otrzymać polecenie 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.
Automaty i dzieci
Sloty umożliwiają kompozycję, ponieważ pozwalają zagnieżdżać komponenty.
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ą tworzone 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 elementy podrzędne to elementy HTML dołączone 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 dodanie wielu slotów jest w zasadzie tym samym co odziedziczenie 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 miejscu docelowym
Gniazda będą wyświetlać swoje poddrzewo, gdy nie będą do nich rzutowane ż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 elementy 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. Odsyłacze
Czasami deweloper może potrzebować dostępu do interfejsu API elementu HTMLElement.
W tej sekcji dowiesz się, jak uzyskiwać 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.
Lit „References” with @query
Lit działa blisko przeglądarki i tworzy bardzo cienką warstwę abstrakcji nad natywnymi funkcjami przeglądarki.
Odpowiednikiem elementu refs w bibliotece React w bibliotece 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(tworząc getter 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 działania 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
Z tej sekcji dowiesz się, jak zapośredniczać stan 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ą stan dzieciom 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 przekazuje stan w przeciwnym kierunku, więc komponenty React rzadko wysyłają i nasłuchują 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ę, że wartość addToCounter jest przekazywana do obu instancji 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żywaaddToCounter, aby aktualizowaćcountostepprzy każdym kliknięciu.
Podobną implementację MyCounter można uzyskać w Lit. Zwróć uwagę, że parametr addToCounter nie jest przekazywany do funkcji counter-button. Zamiast tego wywołanie zwrotne jest powiązane jako detektor zdarzeń ze zdarzeniem @update-counter w 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ść
counter-buttonstepza 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 stylizowania 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 css oznaczonego literału szablonu.
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 otagowanego CSS z powiązaniem.
- Ustawia kolory 2
h1z identyfikatorami.
Zalety używania tagu szablonu css:
- Analizowanie raz na klasę a raz na instancję
- Wdrożony z myślą o ponownym wykorzystaniu 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 w przypadku każdej klasy, jak w przypadku szablonu oznaczonego tagiem css. Dodatkowo deduplikacja w przeglądarce jest znacznie szybsza.CSSResult
Tagi linków
W przypadku stylów możesz też użyć elementu <link rel="stylesheet"> w szablonie, ale nie jest to zalecane, ponieważ może powodować początkowe miganie 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. Większość porównań w przypadku oznaczonych literałów szablonu odbywa się jednak w C++ przeglądarki.
Jeśli chcesz zacząć używać literałów szablonu oznaczonych 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 korzysta z preprocesora, który przekształca JSX w 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 oznaczonych literałów szablonu, które można uruchamiać w przeglądarce bez transpilacji ani preprocesora. Oznacza to, że aby rozpocząć pracę 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 Reacta (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 deweloperskie, co zmusza do pisania specjalnych otoczek 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 ich używać w różnych platformach.
- Może być leniwie rejestrowany za pomocą
customElements.definei „wypełniać” 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, producenci 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 różnych platform przeważają nad korzyściami wynikającymi z warstwy abstrakcji komponentów. W rzeczywistości zespół Lit w ramach projektu lit-ssr rozwiązał główne problemy z rejestracją JavaScriptu. Niektóre firmy, np. 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ść ciągu atrybutu w number, a następnie wprowadzana jest złożona struktura danych z attribute:false, która dezaktywuje obsługę atrybutó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ą właściwościami tej samej klasy.
shouldComponentUpdate
- Równowartość w litrach:
shouldUpdate - Wywoływany przy pierwszym renderowaniu, w przeciwieństwie do Reacta
- Podobny do funkcji
shouldComponentUpdatew React
getSnapshotBeforeUpdate
W przypadku Lit getSnapshotBeforeUpdate jest podobny 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ę w niej 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 zostaną wprowadzone przed wywołaniemsuper.update. - Dobre miejsce do przechwytywania informacji z interfejsu DOM otaczającego 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
W poprzedniej sekcji nie wspomnieliśmy o kilku wywołaniach zwrotnych cyklu życia, ponieważ nie mają one odpowiedników w React. Są to:
attributeChangedCallback
Jest wywoływana, gdy zmieni się jeden z atrybutów observedAttributes elementu. Zarówno observedAttributes, jak i attributeChangedCallback są częścią specyfikacji elementów niestandardowych i są implementowane przez Lit w tle, 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 cyklu ż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 oczekiwać, 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
Ta właściwość ma wartość 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 literatury
Cykl życia aktualizacji składa się z 3 etapów:
- Przed aktualizacją
- Aktualizuj
- Po aktualizacji
Przed aktualizacją

Po requestUpdate oczekiwana jest zaplanowana aktualizacja.
Aktualizuj

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 hookami lub efektami wzorzec hooków zwykle nie wystarcza, a rozwiązanie oparte na klasach, takie jak kontrolery reaktywne, sprawdza się lepiej.
Haczyki 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
- 404, gdy dociera do użytkownika 11 (zgodnie z założeniami)
- Błąd przerwania, jeśli pobieranie z 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
- Emituje:
- Nazwa użytkownika
- Czy pobieranie jest w toku
- wszelkie komunikaty o błędach;
- Wywołanie zwrotne, które umożliwia przerwanie pobierania.
- 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 slotu 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>
`;
}
}
Odsyłacze
Generowanie odwołań
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 za pomocą dekoratora Lit @query lub odwołania React.
React to funkcjonalny potok, który tworzy komponenty React, a nie elementy HTML. Ponieważ element 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 poziomie głównym w zakresie.
Wysyłanie zapytań do wielu elementów
W przykładzie poniżej dekorator @queryAll zwróci 2 akapi w głównym elemencie podrzędnym 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 istnieje również w zakresie przeglądarki, jest dostępna dla Lit. W Lit powstało wiele wspaniałych bibliotek, które wykorzystują istniejące systemy 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 wykorzystywać 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żna wykorzystać w komponencie Lit.
Styl
Shadow DOM
Aby natywnie hermetyzować style i DOM w niestandardowym elemencie, Lit używa Shadow DOM. Shadow Roots generują 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ą widoczne.
Shadow DOM wprowadza też do specyfikacji CSS nowe pojęcia i selektory:
: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 konwencjonalnego motywu, który zwykle jest podejściem do tagów stylów odgórnych. 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łównym 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 wyświetlanie dwóch różnych pakietów: jednego dla IE 11 i jednego dla nowoczesnych przeglądarek. Daje to kilka korzyści:
- Obsługa ES6 jest szybsza i obejmuje 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.