1. Wprowadzenie
Co to jest Lit
Lit to prosta biblioteka do tworzenia szybkich i lekkich komponentów sieciowych, które działają na dowolnej platformie lub nie mają żadnej platformy. Za pomocą Lit możesz tworzyć między innymi komponenty, aplikacje i systemy, które można udostępniać.
Czego się nauczysz
Jak przenieść niektóre pojęcia z React do Lit, np.:
- JSX i szablony
- Komponenty i rekwizyty
- Stan i cykl życia
- Hooks
- Dzieci
- Odwołania
- Stan pośredniczenia
Co utworzysz
Po zakończeniu tego ćwiczenia w Codelabs będzie można przekonwertować koncepcje komponentu React na ich odpowiedniki Lit.
Czego potrzebujesz
- najnowsza wersja przeglądarki Chrome, Safari, Firefox lub Edge;
- znajomość HTML, CSS, JavaScriptu i Narzędzi deweloperskich w Chrome;
- znajomość Reacta;
- (Zaawansowane) Jeśli chcesz mieć jak najlepsze warunki do programowania, pobierz VS Code. Potrzebna będzie też lit-plugin na potrzeby kodu VS Code i NPM.
2. Lit vs React
Podstawowe koncepcje i możliwości Lita są pod wieloma względami podobne do rozwiązań React, ale cechuje się też kilkoma kluczowymi cechami:
Jest mały
Ilość podświetlenia jest niewielka: ilość treści jest zmniejszona do około 5 KB i spakowana w formacie gzip, natomiast React + ReactDOM: ponad 40 KB.
Szybkość
W publicznych testach porównawczych, które porównują system szablonów Lit, lit-html, z VDOM React, lit-html jest 8–10% szybszy od React w najgorszym przypadku i ponad 50% szybszy w bardziej typowych przypadkach użycia.
LitElement (klasa podstawowa komponentu Lite) w przypadku lit-html ma minimalny narzut, ale przekracza wydajność React o 16–30% przy porównywaniu funkcji komponentu, takich jak wykorzystanie pamięci, interakcje i czas uruchamiania.
Nie wymaga kompilacji
Dzięki nowym funkcjom przeglądarki, takim jak moduły ES i otagowane literale szablonów, Lit nie wymaga kompilacji przed uruchomieniem. Oznacza to, że można skonfigurować środowisko programistyczne za pomocą tagu skryptu + przeglądarki + serwer.
Dzięki modułom ES i nowoczesnym sieciom CDN, takim jak Skypack czy UNPKG, być może nie potrzebujesz już NPM, aby zacząć korzystać z usługi.
Jeśli chcesz, nadal możesz tworzyć i optymalizować kod Lit. Niedawna konsolidacja programistów wokół natywnych modułów ES sprawdza się w przypadku platformy – Lit to po prostu zwykły JavaScript i nie trzeba używać interfejsu wiersza poleceń dla konkretnej platformy ani obsługi kompilacji.
Niezależne od platformy
Komponenty Lit opierają się na zestawie standardów internetowych zwanych komponentami internetowymi. Oznacza to, że utworzenie komponentu w Lit będzie działać zarówno w obecnej, jak i przyszłej platformie. Jeśli obsługuje elementy HTML, obsługuje też komponenty internetowe.
Jedynymi problemami dotyczącymi interoperacyjności frameworków są te, w których frameworki mają ograniczone wsparcie dla DOM. React jest jedną z tych platform, ale ta metoda pozwala uciec w sytuacjach dzięki Refs in React.
Zespół Lit pracował nad eksperymentalnym projektem o nazwie @lit-labs/react
, który automatycznie analizuje komponenty Lit i generuje zewnętrzny moduł React, dzięki czemu nie musisz używać odwołań.
Dodatkowo narzędzie Custom Elements Everywhere pokazuje, które platformy i biblioteki dobrze współpracują z elementami niestandardowymi.
Pierwsza klasa obsługi formatu TypeScript
Chociaż cały kod Lit można napisać w JavaScript, Lit jest napisany w TypeScript, a zespół Lit zaleca, aby deweloperzy również używali tego języka.
Zespół Lit współpracuje ze społecznością Lit, pomagając w prowadzeniu projektów, w których sprawdzanie typów i analizy typu TypeScript oraz szablony Lit jest możliwe zarówno podczas programowania, jak i budowania za pomocą lit-analyzer
i lit-plugin
.
Narzędzia dla programistów są wbudowane w przeglądarkę
Komponenty Lit to tylko 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 dla programistów, wybrać element i sprawdzić jego właściwości lub stan.
Został stworzony z myślą o renderowaniu po stronie serwera (SSR)
Lit 2 został stworzony z myślą o obsługiwaniu żądań zwrotnych. W momencie pisania tego ćwiczenia z programowania zespół Lit nie wprowadził jeszcze narzędzi SSR w wersji stabilnej, ale zespół Lit wdrożył już w usługach Google komponenty renderowane po stronie serwera i przetestował SSR w aplikacjach React. Zespół Lit wkrótce udostępni te narzędzia na GitHubie.
Tymczasem możesz śledzić postępy zespołu Lit tutaj.
niskie koszty wdrożenia,
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 konwertować całej aplikacji naraz, ponieważ komponenty web działają w ramach innych frameworków.
Czy masz już całą aplikację w Lit i chcesz ją zastąpić inną? Można umieścić obecną aplikację Lit wewnątrz nowej platformy i przenieść do niej dowolne elementy do komponentów nowej platformy.
Dodatkowo wiele nowoczesnych frameworków obsługuje dane wyjściowe w komponentach internetowych, co oznacza, że zwykle mogą one pasować do elementu Lit.
3. Konfigurowanie i poznawanie Playground
Możesz wykonać to zadanie na 2 sposoby:
- Całkowicie możesz to zrobić online, w przeglądarce
- (Zaawansowane) Możesz to zrobić na komputerze lokalnym za pomocą VS Code.
Uzyskiwanie dostępu do kodu
W trakcie ćwiczeń z programowania dostępne będą takie linki do placu zabaw Lit:
Playground to piaskownica kodu, która działa całkowicie w przeglądarce. Może kompilować i uruchamiać pliki TypeScript i JavaScript, a także automatycznie decydować o importowaniu do modułów węzłów, np.
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://cdn.skypack.dev/lit';
Możesz przejść cały samouczek na Lit playground, korzystając z tych punktów kontrolnych jako punktów wyjścia. Jeśli korzystasz z VS Code, możesz użyć tych punktów kontrolnych do pobrania kodu początkowego dla dowolnego kroku, a także do wykorzystania ich do sprawdzenia swojej pracy.
Poznawanie interfejsu oświetlonego placu zabaw
Zrzut ekranu z interfejsem Lit playground pokazuje sekcje, których użyjesz w tym Codelab.
- Selektor plików Zwróć uwagę na przycisk plusa...
- Edytor plików.
- Podgląd kodu.
- Przycisk ponownego załadowania.
- Przycisk Pobierz.
Konfiguracja VS Code (zaawansowane)
Oto zalety tej konfiguracji VS Code:
- Sprawdzanie typu szablonu
- Analiza szablonów i autouzupełnianie
Jeśli masz już zainstalowaną wtyczkę NPM, VS Code (z wtyczką lit-plugin) i wiesz, jak korzystać z tego środowiska, możesz po prostu pobrać i uruchomić te projekty, wykonując te czynności:
- Naciśnij przycisk pobierania.
- Wyodrębnij zawartość pliku tar do katalogu.
- (W przypadku rozwiązania do rozwiązywania problemów) skonfiguruj szybki plik tsconfig, który zwraca moduły es i kod es2015+
- Zainstaluj serwer programisty, który obsługuje specyfikatory samego modułu (zespół Lit zaleca adres @web/dev-server).
- Oto przykład:
package.json
- Oto przykład:
- Uruchom serwer deweloperski i otwórz przeglądarkę (jeśli używasz pakietu @web/dev-server, możesz użyć
npx web-dev-server --node-resolve --watch --open
).- Jeśli używasz przykładowego
package.json
, użyjnpm run dev
- Jeśli używasz przykładowego
4. JSX i tworzenie szablonów
W tej sekcji poznasz podstawy tworzenia szablonów w Lit.
Szablony JSX i Lit
JSX to rozszerzenie składni JavaScript, które pozwala użytkownikom Reacta na łatwe tworzenie szablonów w kodzie JavaScript. Szablony Lit służą do podobnego celu: wyrażania interfejsu użytkownika komponentu jako funkcji jego stanu.
Podstawowa składnia
W React renderowanie JSX „Witaj, świecie” wygląda tak:
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 tym przykładzie są 2 elementy i uwzględniona zmienna „name”. W Lit wykonaj 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
);
Pamiętaj, że szablony Lit nie wymagają fragmentu React, aby grupować w swoich szablonach wiele elementów.
W Lit szablony są zapakowane html
szablonem z tagami LITeralnym – tak się składa, że to tam wzięła swoją nazwę.
Wartości szablonu
Szablony Lit mogą akceptować inne szablony Lit, zwane TemplateResult
. Możesz na przykład opakować tag name
kursywą (<i>
) i zapakować go w literał szablonu N.B.. Pamiętaj, aby użyć znaku grawisu (`
), a nie 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 TemplateResult
może przyjmować tablice, ciągi znaków, inne TemplateResult
oraz dyrektywy.
W przypadku ćwiczenia spróbuj przekonwertować ten 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 atrybutów
Jedną z największych różnic między składnią JSX a Lit jest składnia wiązania danych. Na przykład użyj tych danych wejściowych w 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:
- Ustawia wartość wyłączone na zdefiniowaną zmienną (w tym przypadku fałsz)
- Ustawia klasę na
static-class
i 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 wartości logicznej w celu przełączania atrybutu disabled
.
Następnie występuje powiązanie bezpośrednio z atrybutem class
, a nie className
. Do atrybutu class
można dodać wiele powiązań, chyba że używasz dyrektywy classMap
, która jest deklaratywnym narzędziem do przełączania klas.
W końcu właściwość value
jest ustawiana na wejściu. W przeciwieństwie do React nie spowoduje to ustawienia elementu wejściowego w trybie tylko do odczytu, ponieważ jest zgodny z natywną implementacją i zachowaniem danych wejściowych.
Składnia powiązania funkcji Lit prop
html`<my-element ?attribute-name=${booleanVar}>`;
- Wstęp
?
to składnia wiązania służąca do przełączania atrybutu w elemencie - Odpowiednik:
inputRef.toggleAttribute('attribute-name', booleanVar)
- Przydatne w przypadku elementów, które używają atrybutu
disabled
jakodisabled="false"
, są nadal interpretowane jako prawdziwe przez DOM, ponieważinputElement.hasAttribute('disabled') === true
html`<my-element .property-name=${anyVar}>`;
- Prefiks
.
to składnia powiązania służąca do ustawiania właściwości elementu. - Odpowiednik:
inputRef.propertyName = anyVar
- Dobrze nadaje się do przekazywania złożonych danych, takich jak obiekty, tablice lub klasy.
html`<my-element attribute-name=${stringVar}>`;
- Wiązanie z atrybutem elementu
- Odpowiednik:
inputRef.setAttribute('attribute-name', stringVar)
- Sprawdza się w przypadku wartości podstawowych, selektorów reguł stylu i selektora zapytań
Moduły obsługi przekazywania
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 tym przykładzie zostały zdefiniowane dane wejściowe:
- Zapisuj słowo „kliknij” po kliknięciu tekstu wejściowego
- Rejestrowanie wartości danych wejściowych, gdy użytkownik wpisze 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
z parametrem @click
został dodany detektor.
Następnie, zamiast używać obiektu onChange
, możesz utworzyć powiązanie z natywnym zdarzeniem input
przeglądarki <input>
, ponieważ natywne zdarzenie change
uruchamia się tylko wtedy, gdy jest ustawione zdarzenie blur
(wykorzystanie abstraktów z reakcji na te zdarzenia).
Składnia modułu obsługi zdarzeń Lit
html`<my-element @event-name=${() => {...}}></my-element>`;
- Prefiks
@
to składnia powiązania detektora zdarzeń - Odpowiednik:
inputRef.addEventListener('event-name', ...)
- Używa natywnych nazw zdarzeń DOM.
5. Komponenty i rekwizyty
W tej sekcji poznasz komponenty i funkcje klasy Lit. Stan i funkcje wywołujące omawiamy bardziej szczegółowo w kolejnych sekcjach.
Komponenty klasowe i LitElement
Odpowiednikiem litowym komponentu klasy React jest LitElement. Pojęcie „właściwości reaktywnych” w przypadku Lita 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:
- Renderowanie
name
- Ustawia wartość domyślną
name
na pusty ciąg znaków (""
) - Przepisuje
name
na"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);
A w pliku HTML:
<!-- index.html -->
<head>
<script type="module" src="./index.js"></script>
</head>
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
Przegląd tego, co dzieje się w przykładzie powyżej:
@property({type: String})
name = '';
- Definiuje publiczną właściwość reaktywną – część publicznego interfejsu API komponentu
- Wyświetla atrybut (domyślnie) oraz właściwość komponentu.
- Określa, jak przekształcić atrybut komponentu (który jest ciągiem znaków) w wartość.
static get properties() {
return {
name: {type: String}
}
}
- Pełni taką samą funkcję jak dekorator
@property
, ale działa natywnie w języku JavaScript
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
- Jest ona wywoływana po zmianie dowolnej właściwości reaktywnej.
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
...
}
- Połączenie nazwy tagu elementu HTML z definicją klasy
- Zgodnie ze standardem elementów niestandardowych nazwa tagu musi zawierać łącznik (-).
this
w LitElement odnosi się do instancji elementu niestandardowego (w tym przypadku<welcome-banner>
).
customElements.define('welcome-banner', WelcomeBanner);
- Jest to odpowiednik JavaScriptu dekoratora TS
@customElement
<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ść
name
na'Elliott'
Komponenty funkcji
Lit nie interpretuje komponentu funkcji w sposób dokładny, ponieważ nie używa JSX ani preprocesora. Stworzenie funkcji, która przyjmuje właściwości i renderuje DOM na ich podstawie, jest jednak dość proste. Na przykład:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Elliott"/>
ReactDOM.render(
element,
mountNode
);
W przypadku Lit:
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 poznasz stan i cykl życia Lit.
Stan
Pojęcie „właściwości reaktywnych” Lita łączy stan i rekwizyty Reacta. Zmienione właściwości reaktywne mogą aktywować cykl życia komponentu. Właściwości reaktywne występują w 2 wariantach:
Public reactive properties
// 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 zmienne;
- Publiczny interfejs API dostępny i ustawiany przez konsumentów komponentu
Wewnętrzny stan reakcji
// 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 React, ale zmienny
- 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 React, ale występują 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 licencie to też
constructor
- Nie musisz przekazywać niczego do super połączenia.
- Wywoływany przez (niepełna lista):
document.createElement
document.innerHTML
new ComponentClass()
- Jeśli na stronie znajduje się nazwa tagu, która nie została zaktualizowana, a definicja została załadowana i zarejestrowana za pomocą atrybutu
@customElement
lubcustomElements.define
- Funkcja podobna do
constructor
w React
render
// React
render() {
return <div>Hello World</div>
}
// Lit
render() {
return html`<div>Hello World</div>`;
}
- Odpowiednik licencyjny to również
render
- Może zwracać dowolny wynik, który można wyświetlić, np.
TemplateResult
lubstring
itp. - Podobnie jak w przypadku React,
render()
powinna być czystą funkcją - Wyświetla dane z węzła zwracanego przez funkcję
createRenderRoot()
(domyślnieShadowRoot
).
componentDidMount
componentDidMount
to połączenie wywołań zwrotnych cyklu życia firstUpdated
i connectedCallback
.
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 przy pierwszym renderowaniu szablonu komponentu w katalogu głównym komponentu.
- Jest wywoływany tylko wtedy, gdy element jest połączony, np. nie jest wywoływany za pomocą
document.createElement('my-component')
, dopóki ten węzeł nie zostanie dołączony do drzewa DOM. - To dobre miejsce do konfiguracji komponentu, która wymaga interfejsu DOM renderowanego przez komponent.
- W przeciwieństwie do zmian
componentDidMount
we właściwościach reaktywnych w obiekciefirstUpdated
powstanie ponowne renderowanie, ale przeglądarka zwykle umieszcza zmiany w tej samej ramce. Jeśli te zmiany nie wymagają dostępu do DOM głównego katalogu, należy zwykle wprowadzić je w interfejsiewillUpdate
.
connectedCallback
// React
componentDidMount() {
this.window.addEventListener('resize', this.boundOnResize);
}
// Lit
connectedCallback() {
super.connectedCallback();
this.window.addEventListener('resize', this.boundOnResize);
}
- Wywoływane za każdym razem, gdy element niestandardowy jest wstawiony do drzewa DOM.
- W przeciwieństwie do komponentów React, gdy elementy niestandardowe zostaną odłączone od DOM, nie są one usuwane, a więc mogą być „połączone” wielokrotnie.
- Funkcja
firstUpdated
nie będzie już wywoływana
- Funkcja
- Przydatne do ponownego inicjowania DOM lub ponownego dołączania detektorów zdarzeń, które zostały wyczyszczone po rozłączeniu
- Uwaga: funkcja
connectedCallback
może zostać wywołana przedfirstUpdated
, więc przy pierwszym wywołaniu 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);
}
}
- Odpowiednikiem w języku literackim jest
updated
(używane w języku angielskim w formie czasu przeszłego od „update”). - W przeciwieństwie do reakcji w reakcji obiekt
updated
jest również wywoływany podczas początkowego renderowania. - Działa podobnie jak
componentDidUpdate
React
componentWillUnmount
// React
componentWillUnmount() {
this.window.removeEventListener('resize', this.boundOnResize);
}
// Lit
disconnectedCallback() {
super.disconnectedCallback();
this.window.removeEventListener('resize', this.boundOnResize);
}
- Odpowiednik w literaturze jest podobny do tekstu
disconnectedCallback
- W przeciwieństwie do komponentów React, gdy elementy niestandardowe są odłączane od DOM, komponent nie jest usuwany.
- W przeciwieństwie do
componentWillUnmount
funkcjadisconnectedCallback
jest wywoływana po usunięciu elementu z drzewa. - DOM w korzenim nadal jest połączony z drzewem podrzędnym
- Przydatne do czyszczenia detektorów zdarzeń i niestabilnych odwołań, aby przeglądarka mogła usunąć komponent z pamięci podręcznej.
Ćwiczenie
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 następujące czynności:
- Wyświetli się komunikat „Hello, World! jest”, a potem wyświetla godzinę,
- Co sekundę będzie aktualizować zegar.
- Po zdjęciu z zaczepu powoduje wyczyszczenie interwału wywołująco-odpowiadającego tick.
Najpierw zacznij od deklaracji klasy 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 date
i oznacz go jako wewnętrzną właściwość reaktywną za pomocą @state
, ponieważ użytkownicy komponentu nie będą ustawiać wartości 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 zaimplementuj metodę znaczników.
tick() {
this.date = new Date();
}
Kolejny krok to implementacja componentDidMount
. Analog litu to mieszanina firstUpdated
i connectedCallback
. W przypadku tego komponentu wywołanie metody tick
za pomocą metody setInterval
nie wymaga dostępu do DOM w katalogu głównym. Dodatkowo, gdy element zostanie usunięty z drzewa dokumentu, interwał zostanie wyczyszczony, więc jeśli zostanie ponownie dołączony, interwał będzie musiał zostać rozpoczęty od początku. Dlatego lepszą opcją 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ł znaku wyboru po odłączeniu elementu od drzewa dokumentu.
// Lit (TS & JS)
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
Po połączeniu wszystkiego 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. Hooks
W tej sekcji dowiesz się, jak przenosić koncepcje React Hook do Lit.
Pojęcia związane z hakami w React
Punkty zaczepienia reakcji umożliwiają zaczerpanie się komponentów funkcji ze stanu. Ma to kilka zalet.
- Ułatwiają ponowne użycie logiki stanowej.
- Pomoc w podziale komponentu na mniejsze funkcje
Komponenty oparte na funkcjach rozwiązywały też pewne problemy ze składnią klasową React, takie jak:
- Trzeba przesłać przejazd (
props
) z:constructor
do:super
- Niepożądana inicjalizacja właściwości w:
constructor
- Był to powód podany przez zespół React, ale rozwiązany w ES2019
- Problemy spowodowane tym, że atrybut
this
nie odnosi się już do komponentu
Koncepcje funkcji reakcji w Lit
Jak wspomnieliśmy w sekcji Komponenty i rekwizyty, Lit nie oferuje sposobu tworzenia elementów niestandardowych na podstawie funkcji, ale LitElement rozwiązuje większość głównych problemów z komponentami klas 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?
- Funkcja
constructor
nie przyjmuje żadnych argumentów. - Wszystkie
@event
powiązań są automatycznie powiązane z elementemthis
this
w większości przypadków odnosi się do odwołania elementu niestandardowego- Właściwości klasy mogą teraz być instancjonowane jako elementy klasy. Upraszcza to implementacje konstruktorowe
Kontrolery reaktywne
Podstawowe koncepcje dotyczące uchwytów występują w Lit jako kontrolery reaktywne. Reaktywne wzorce kontrolera pozwalają współużytkować logikę stanową, dzielić komponenty na mniejsze, bardziej modułowe fragmenty oraz łączyć się z cyklem aktualizacji elementu.
Reaktywność kontrolera to interfejs obiektu, który może wykorzystywać cykl aktualizacji kontrolera hosta, np. LitElement.
Cykl życia ReactiveController
i reactiveControllerHost
:
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ą funkcji addController
, cykl życia kontrolera będzie wywoływany razem z cyklem życia hosta. Przypomnijmy sobie przykład zegara 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 mamy prosty zegar, który wykonuje następujące czynności:
- Wyświetla ona „Hello World!”. jest”, a potem wyświetla godzinę,
- Co sekundę będzie aktualizować zegar.
- Po zdjęciu z zaczepu powoduje wyczyszczenie interwału wywołująco-odpowiadającego tick.
Tworzenie szkieletu komponentu
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 przejdź do clock.ts
i utwórz zajęcia dla użytkownika 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() {
}
}
Reaktywny kontroler można utworzyć w dowolny sposób, o ile ma wspólny interfejs ReactiveController
, ale używa klasy z constructor
, która może przyjmować interfejs ReactiveControllerHost
, a także wszelkie inne właściwości potrzebne do inicjowania kontrolera. To wzorzec, którego zespół Lit preferuje w większości podstawowych przypadków.
Teraz musisz przetłumaczyć wywołania zwrotne cyklu życia React na wywołania zwrotne kontrolera. W skrócie:
componentDidMount
- Do LitElement's
connectedCallback
- Do
hostConnected
kontrolera
- Do LitElement's
ComponentWillUnmount
- Do LitElement's
disconnectedCallback
- Do
hostDisconnected
kontrolera
- Do LitElement's
Więcej informacji o przekształcaniu cyklu życia React w cykl życia Lit znajdziesz w sekcji Stan i cykl życia.
Następnie zaimplementuj metodę zwracającą wywołanie zwrotne hostConnected
i metodę tick
oraz wyczyścić interwał w metodie hostDisconnected
tak, jak to pokazano 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 kontrolera hosta (który jest komponentem <my-element>
), a następnie użyć kontrolera w metodzie render
.
Uruchamianie ponownego renderowania w kontrolerze
Zwróć uwagę, że wyświetla się godzina, ale godzina się nie aktualizuje. Dzieje się tak, ponieważ kontroler ustawia datę co sekundę, ale host nie jest aktualizowany. Dzieje się tak, ponieważ date
zmienia się w klasie ClockController
, a nie w komponencie. Oznacza to, że po ustawieniu na kontrolerze obiektu date
musisz poinformować hosta, że ma uruchomić cykl aktualizacji z użyciem host.requestUpdate()
.
// Lit (TS & JS) - clock.ts / clock.js
private tick() {
this.date = new Date();
this.host.requestUpdate();
}
Teraz zegar powinien tykać!
Więcej informacji o porównaniu typowych zastosowań z zastosowaniem funkcji hook znajdziesz w sekcji Zaawansowane tematy – hooki.
8. Dzieci
Z tej sekcji dowiesz się, jak używać przedziałów do zarządzania elementami podrzędnymi w Lit.
Sloty i dzieci
Slots umożliwiają tworzenie kompozycji, ponieważ pozwalają zagnieżdżać komponenty.
W Reactie elementy podrzędne są dziedziczone za pomocą właściwości. Przedział domyślny to props.children
, a funkcja render
określa, gdzie znajduje się boks domyślny. 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 elementy podrzędne są tworzone w funkcji renderowania z elementami boksu. Zauważ, że elementy podrzędne nie są dziedziczone w taki sam sposób jak w React. W Lit elementy podrzędne to elementy HTMLElements dołączone do boksów. Ten załącznik nazywa się Odwzorowanie.
@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 zasadniczo 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>
spowoduje utworzenie większej liczby slotów w Lit. W atrybucie name
zdefiniowano wiele przedziałów: <slot name="slot-name">
. Dzięki temu mogą zadeklarować, do którego przedziału czasu zostaną 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>
`;
}
}
Domyślna zawartość boksu
Jeśli w danym gnieździe nie ma żadnych węzłów, wyświetla się jego poddrzewo. Gdy węzły są rzutowane na slot, nie wyświetla on swojego poddrzewa, a zamiast tego wyświetla węzły rzutowane.
@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>
`;
}
}
Przypisz elementy podrzędne do przedziałów
W React elementy podrzędne są przypisywane do slotów 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 elementy podrzędne są przypisywane do przedziałó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 slotu (np. <slot>
) i nie ma też slotu, który ma atrybut name
(np. <slot name="foo">
) pasujący do atrybutu slot
elementów podrzędnych elementu niestandardowego (np. <div slot="foo">
), ten węzeł nie będzie wyświetlany.
9. Odwołania
Czasami programista może potrzebować dostępu do interfejsu API elementu HTMLElement.
Z tej sekcji dowiesz się, jak pobierać odwołania do elementów w Lit.
Odwołania w React
Komponent React jest transpilowany w serii wywołań funkcji, które po wywołaniu tworzą wirtualny DOM. Ten wirtualny DOM jest interpretowany przez ReactDOM i renderuje element HTMLElements.
W React referencje to miejsce w pamięci na 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 będzie wykonywać te czynności:
- Renderowanie pustego pola tekstowego i przycisku z tekstem
- Zaznacz dane wejściowe po kliknięciu przycisku
Po pierwszym renderowaniu React ustawia wartość inputRef.current
na wygenerowaną wartość HTMLInputElement
za pomocą atrybutu ref
.
Lit „References” with @query
Lit znajduje się blisko przeglądarki i ma bardzo małą abstrakcję względem natywnych funkcji przeglądarki.
Odpowiednikiem klasy refs
w Lit jest element HTMLElement zwrócony 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 wykona te działania:
- Definiuje właściwość w
MyElement
przy użyciu dekoratora@query
(tworząc metodę pobierania dlaHTMLInputElement
). - Deklaruje i dołącza wywołanie zwrotne zdarzenia kliknięcia o nazwie
onButtonClick
. - Aktywuje dane wejściowe po kliknięciu przycisku
W języku JavaScript dekoratory @query
i @queryAll
wykonują odpowiednio querySelector
i querySelectorAll
. To jest odpowiednik JavaScriptu @query('input') inputEl!: HTMLInputElement;
get inputEl() {
return this.renderRoot.querySelector('input');
}
Gdy komponent Lit zapisze szablon metody render
w korzeniach komponenta my-element
, dekorator @query
pozwoli komponentowi inputEl
zwrócić pierwszy element input
znaleziony w korzeniach renderowania. Jeśli @query
nie znajdzie podanego elementu, zwróci wartość null
.
Jeśli w korzeniach renderowania jest kilka elementów input
, funkcja @queryAll
zwróci listę węzłów.
10. Stan pośrednictwa
W 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. Rodzice przekazują stan dzieciom za pomocą rekwizytów. Dzieci komunikują się z rodzicami za pomocą wywołań zwrotnych dostępnych w rekwizytach.
const CounterButton = (props) => {
const label = props.step < 0
? `- ${-1 * props.step}`
: `+ ${props.step}`;
return (
<button
onClick={() =>
props.addToCounter(props.step)}>{label}</button>
);
};
W tym przykładzie komponent React wykonuje te działania:
- Tworzy etykietę na podstawie wartości
props.step
. - Renderuje przycisk z etykietą +step lub -step.
- Aktualizuje komponent nadrzędny przez wywołanie metody
props.addToCounter
z argumentemprops.step
jako argumentem przy kliknięciu
Chociaż można przekazywać wywołania zwrotne w Lit, konwencjonalne wzorce są inne. W przykładzie poniżej komponent React może być napisany jako komponent Lit:
@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 powyższym przykładzie komponent Lit wykona te działania:
- Tworzenie usługi reaktywnej
step
- Wyślij zdarzenie niestandardowe o nazwie
update-counter
z wartościąstep
elementu po kliknięciu
Zdarzenia w przeglądarce przesuwają się od elementów podrzędnych do elementów nadrzędnych. Wydarzenia umożliwiają dzieciom przekazywanie informacji o zdarzeniach interakcji i zmianach stanu. React przekazuje stan w przeciwnym kierunku, więc rzadko zdarza się, aby komponenty React wysyłały i odbierały zdarzenia w taki sam sposób jak komponenty Lit.
Komponenty stanowe
W React do zarządzania stanem często używa się haka. Komponent MyCounter
można utworzyć, używając komponentu CounterButton
. Zwróć uwagę, że parametr addToCounter
jest przekazywany do obu instancji funkcji 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>
);
};
W przykładzie powyżej:
- Tworzy stan
count
. - Tworzy wywołanie zwrotne, które dodaje numer do stanu
count
. CounterButton
używa narzędziaaddToCounter
do aktualizowania wartościcount
dostep
po każdym kliknięciu.
Podobną implementację MyCounter
można uzyskać w Lit. Zwróć uwagę, że metoda addToCounter
nie jest przekazywana do pola counter-button
. Wywołanie zwrotne jest natomiast powiązane jako detektor zdarzenia @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>
`;
}
}
Przykład powyżej wygląda tak:
- Tworzy właściwość reaktywną o nazwie
count
, która aktualizuje komponent po zmianie wartości. - Łączy wywołanie zwrotne
addToCounter
z detektorem zdarzeń@update-counter
- Aktualizuje pole
count
, dodając wartość znajdującą się w parametrzedetail.step
zdarzeniaupdate-counter
- Ustawia wartość
step
elementucounter-button
za pomocą atrybutustep
.
W Lit bardziej konwencjonalne jest używanie właściwości reaktywnych do rozpowszechniania zmian z elementów nadrzędnych na podrzędne. Warto też korzystać z systemu zdarzeń przeglądarki, aby szczegółowe informacje były przekazywane od dołu do góry.
Takie podejście jest zgodne ze sprawdzonymi metodami i zgodnie z celem Lit, którym jest zapewnienie obsługi komponentów internetowych na różnych platformach.
11. Styl
W tej sekcji dowiesz się więcej o stylizacji w Lit.
Styl
Lit oferuje wiele sposobów określania stylu elementów, a także rozwiązanie wbudowane.
Style wbudowane
Lit obsługuje style wbudowane oraz wiązania 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 są 2 nagłówki, z których każdy ma styl wstawiany w tekście.
Teraz zaimportuj i połącz obramowanie z border-color.js
z pomarańczowym tekstem:
...
import borderColor from './border-color.js';
...
html`
...
<h1 style="color:orange;${borderColor}">This text is orange</h1>
...`
Konieczność obliczania ciągu stylu za każdym razem może być irytująca, dlatego Lit oferuje dyrektywę, która w tym pomaga.
styleMap
styleMap
Instrukcja ułatwia użycie JavaScriptu do ustawiania stylów wstawianych. 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>
`;
}
}
W przykładzie powyżej:
- Wyświetla
h1
z obrzeżeniem i selektorem kolorów - Zmienia
border-color
na wartość z selektora kolorów
Dodatkowo styleMap
, który służy do określania stylów elementu h1
. styleMap
ma składnię podobną do składnia wiązania atrybutów style
w React.
CSSResult
Zalecanym sposobem stylu komponentów jest użycie literału szablonu otagowanego 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>
`;
}
}
W przykładzie powyżej:
- Deklaruje tagowany szablon CSS z wiązaniem
- Ustawia kolory 2 elementów
h1
z identyfikatorami
Zalety korzystania z tagu szablonu css
:
- Przetwarzanie raz na klasę lub raz na instancję
- Moduł został zaimplementowany z myślą o możliwości ponownego użycia.
- Możliwość łatwego rozdzielania stylów na osobne pliki
- Zgodny z kodem polyfill CSS Custom Właściwości
Zwróć też uwagę na tag <style>
w tagu index.html
:
<!-- index.html -->
<style>
h1 {
color: red !important;
}
</style>
Lit pozwala dostosować style komponentów do ich poziomu głównego. Oznacza to, że style nie będą się przenikać. Aby przekazywać style do komponentów, zespół Lit zaleca używanie własnych właściwości CSS, ponieważ mogą one obejmować zakres stylów Lit.
Tagi stylów
Możesz też wstawiać tagi <style>
w swojej treści. Przeglądarka usunie duplikaty tych tagów stylu, ale jeśli umieścisz je w szablonach, będą one analizowane dla wystąpienia komponentu, a nie dla klasy, jak ma to miejsce w przypadku szablonu z tagiem css
. Dodatkowo w przeglądarce usuwanie duplikatów elementów CSSResult
jest znacznie szybsze.
Tagi linków
Użycie w szablonie elementu <link rel="stylesheet">
jest też możliwe, ale nie jest zalecane, ponieważ może spowodować początkowe wyświetlenie treści bez stylów (FOUC).
12. Tematy zaawansowane (opcjonalnie)
JSX i szablony
Lit & Virtual DOM
Lit-html nie zawiera konwencjonalnego wirtualnego DOM, który rozróżnia poszczególne węzły. Wykorzystuje w nim funkcje wydajności wchodzące w skład specyfikacji literału szablonu z tagami ES2015. Literały szablonów otagowane to ciągi literału szablonu z dołączonymi 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 otagowanego 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 tagiem jest funkcja tag
, a funkcja f
zwraca wywołanie literału szablonu oznaczonego tagiem.
Wiele magii wydajności w Lit wynika z faktu, że tablice ciągu znaków przekazywane do funkcji tagu mają ten sam wskaźnik (jak widać w drugim elemencie console.log
). Przeglądarka nie odtwarza nowej tablicy strings
przy każdym wywołaniu funkcji tagu, ponieważ używa tego samego literału szablonu (tj. w tym samym miejscu w AST). Dzięki temu mechanizmy wiązania, analizowania i zapamiętywania szablonów w Lit mogą korzystać z tych funkcji bez nadmiernego obciążania procesu różnicowania.
To wbudowane w przeglądarce zachowanie otagowanych literałów szablonów zapewnia firmie Lit sporą przewagę pod względem wydajności. Większość konwencjonalnych wirtualnych DOM wykonuje większość pracy w języku JavaScript. Jednak znaczniki w literalach szablonów są głównie porównywane w języku C++ przeglądarki.
Jeśli chcesz zacząć korzystać z literałów szablonów otagowanych HTML w React lub Preact, zespół Lit poleca bibliotekę htm
.
Mimo że, podobnie jak w przypadku witryny Google Codelabs i kilku edytorów kodu online, możesz zauważyć, że wyróżnianie składni dosłownej szablonu tagów nie jest zbyt powszechne. Niektóre IDE i edytorzy tekstu obsługują je domyślnie, np. Atom i podświetlanie bloku kodu w GitHub. Zespół Lit ściśle współpracuje też ze społecznością, aby rozwijać projekty takie jak lit-plugin
, czyli wtyczka do VS Code, która umożliwia wyróżnianie składni, sprawdzanie typu i intellisense w projektach Lit.
Lit i JSX + React DOM
JSX nie działa w przeglądarce i zamiast tego korzysta z preprocesora do konwersji JSX na wywołania funkcji JavaScriptu (zwykle przez Babel).
Babel na przykład przekształci to:
const element = <div className="title">Hello World!</div>;
ReactDOM.render(element, mountNode);
do tego:
const element = React.createElement('div', {className: 'title'}, 'Hello World!');
ReactDOM.render(element, mountNode);
Następnie interfejs React DOM przenosi dane wyjściowe z React na rzeczywisty model DOM, czyli właściwości, atrybuty, detektory zdarzeń i inne elementy.
Lit-html używa tagowanych literali szablonów, które mogą działać w przeglądarce bez transpilacji lub preprocesora. Oznacza to, że aby rozpocząć korzystanie z Lit, wystarczy plik HTML, skrypt modułu ES i serwer. Oto skrypt, który w pełni działa 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, ponieważ system szablonów Lit, lit-html, nie używa konwencjonalnego wirtualnego DOM, ale korzysta bezpośrednio z interfejsu DOM API, wersja Lit 2 jest zminifikowana i skompresowana do pliku gzip w porównaniu z reactem (2,8 KB) oraz kompresowaniem 40 KB z minifikacją 40 KB.
Wydarzenia
React korzysta z syntetycznego systemu zdarzeń. Oznacza to, że element Reag-dom musi określać każde zdarzenie, które będzie używane w każdym komponencie, oraz udostępnić detektor zdarzeń CamlCase dla każdego typu węzła. W efekcie JSX nie ma metody definiowania odbiornika zdarzenia niestandardowego, a deweloperzy muszą użyć właściwości ref
, a następnie bezwzględnie zastosować detektor. Uniemożliwia to deweloperom integrowanie bibliotek, które nie uwzględniają tej technologii, i wymaga napisania kodu związanego z reakcją.
Lit-html ma bezpośredni dostęp do modelu DOM i używa zdarzeń natywnych, więc dodawanie detektorów zdarzeń jest niezwykle proste w @event-name=${eventNameListener}
. Oznacza to, że przy dodawaniu detektorów zdarzeń i uruchamianiu zdarzeń wymagane jest mniej analizy środowiska wykonawczego.
Komponenty i rekwizyty
Komponenty React i elementy niestandardowe
Pod gołym niebem LitElement wykorzystuje elementy niestandardowe, by pakować swoje komponenty. Elementy niestandardowe wprowadzają pewne kompromisy między komponentami React w zakresie komponentyzacji (stan i cykl życia są omawiane w sekcji Stan i cykl życia).
Oto kilka zalet systemu komponentów w przypadku elementów niestandardowych:
- są natywne dla przeglądarki i nie wymagają żadnych narzędzi;
- Zgodność z interfejsem API każdej przeglądarki od
innerHTML
idocument.createElement
doquerySelector
- zazwyczaj można go używać w różnych ramach;
- Można zarejestrować leniwie za pomocą funkcji
customElements.define
i modelu DOM „hydrate” (hydrata)
Elementy niestandardowe mają kilka wad w porównaniu z komponentami React:
- Nie można utworzyć elementu niestandardowego bez zdefiniowania klasy (co oznacza brak komponentów funkcjonalnych podobnych do JSX).
- Musi zawierać tag zamykający
- Uwaga: mimo że specyfikacja tagów samozamykających ułatwia pracę programistom, dostawcy przeglądarek nie są z niej zadowoleni, dlatego nowsze specyfikacje zwykle nie zawierają tagów samozamykających.
- Wprowadza dodatkowy węzeł do drzewa DOM, co może powodować problemy z układem
- Musi być zarejestrowany za pomocą kodu JavaScript
Lit postawił na elementy niestandardowe nad systemem elementów niestandardowych, ponieważ są one wbudowane w przeglądarkę, a zespół Lit uważa, że korzyści związane z międzyramami przewyższają korzyści wynikające z warstwy abstrakcji komponentów. W fakcie wysiłki zespołu Lit w zakresie lit-ssr pozwoliły rozwiązać główne problemy z rejestracją JavaScript. Poza tym niektóre firmy, takie jak GitHub, wykorzystują leniwą rejestrację elementów niestandardowych, aby stopniowo ulepszać strony za pomocą opcjonalnych dodatków.
Przekazywanie danych do elementów niestandardowych
W przypadku elementów niestandardowych często panuje przekonanie, że dane można przekazywać tylko jako ciągi znaków. To nieporozumienie prawdopodobnie wynika z tego, że atrybuty elementów można zapisać tylko jako ciągi znaków. Chociaż to prawda, że Lit będzie przesyłać atrybuty w postaci ciągów znaków do zdefiniowanych typów, elementy niestandardowe mogą też akceptować złożone dane jako właściwości.
Na przykład o takiej definicji 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 prosta reaktywna właściwość num
, która konwertuje wartość ciągu atrybutu na number
, a potem wprowadza się złożoną strukturę danych za pomocą attribute:false
, która dezaktywuje obsługę atrybutów Lit.
Aby przekazywać 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ż props i state są tymi samymi właściwościami klasy.
shouldComponentUpdate
- Odpowiednik licencyjny:
shouldUpdate
- Wywoływane przy pierwszym renderowaniu w odróżnieniu od React
- Funkcja podobna do
shouldComponentUpdate
w React
getSnapshotBeforeUpdate
W Lit słowo getSnapshotBeforeUpdate
jest podobne do zarówno update
, jak i willUpdate
willUpdate
- Połączenie zakończone przed
update
- W przeciwieństwie do funkcji
getSnapshotBeforeUpdate
funkcjawillUpdate
jest wywoływana przed funkcjąrender
- Zmiany właściwości reaktywnych w komponencie
willUpdate
nie powodują ponownego uruchamiania cyklu aktualizacji - To dobre miejsce do obliczania wartości właściwości, które zależą od innych właściwości i są używane w dalszej części procesu aktualizacji.
- Ta metoda jest wywoływana na serwerze w SSR, więc nie zaleca się uzyskiwania dostępu do DOM.
update
- Wywołane po
willUpdate
- W przeciwieństwie do funkcji
getSnapshotBeforeUpdate
funkcjaupdate
jest wywoływana przed funkcjąrender
- Zmiany właściwości reaktywnych w
update
nie powodują ponownego wywołania cyklu aktualizacji przed wywołaniem funkcjisuper.update
- Dobre miejsce do przechwytywania informacji z DOM wokół komponentu, zanim wyrenderowany wynik zostanie zapisany w DOM.
- Ta metoda nie jest wywoływana na serwerze w ramach SSR
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 ma dla nich odpowiednika w React. Są to:
attributeChangedCallback
Jest ona wywoływana po zmianie jednego z właściwości observedAttributes
elementu. Zarówno observedAttributes
, jak i attributeChangedCallback
są częścią specyfikacji elementów niestandardowych i zostały zaimplementowane przez Lit, aby zapewnić interfejs API atrybutu dla elementów Lit.
adoptedCallback
Wywoływany, gdy komponent jest przenoszony do nowego dokumentu, np. z HTMLTemplateElement
documentFragment
do głównego document
. Ten wywołanie zwrotne jest też częścią specyfikacji elementów niestandardowych i należy go używać tylko w zaawansowanych przypadkach użycia, gdy komponent zmienia dokumenty.
Inne metody i właściwości cyklu życia
Te metody i właściwości to elementy klasy, które możesz wywoływać, zastępować lub oczekiwać, aby manipulować procesem cyklu życia.
updateComplete
Jest to Promise
, który rozwiązuje się po zakończeniu aktualizacji elementu, 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
Tą metodę należy zastąpić, aby dostosować czas rozwiązania updateComplete
. Jest to typowe, gdy komponent renderuje komponent podrzędny, a cykle renderowania muszą być zsynchronizowane. Przykład:
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, gdy aktualizacje muszą być przeprowadzane synchronicznie lub w przypadku niestandardowego harmonogramu.
hasUpdated
Ta właściwość ma stan true
, jeśli komponent został zaktualizowany co najmniej raz.
isConnected
Ta właściwość będzie częścią specyfikacji elementów niestandardowych i 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 części:
- Przed aktualizacją
- Aktualizuj
- Po aktualizacji
Przed aktualizacją
Po godzinie requestUpdate
nastąpi zaplanowana aktualizacja.
Aktualizuj
Aktualizacja
Hooks
Dlaczego warto używać haków
Elementy przykuwające uwagę zostały wprowadzone do React w celu zastosowania prostych komponentów funkcji, które wymagają stanu. W wielu prostych przypadkach komponenty funkcji z hakami są zwykle znacznie prostsze i łatwiejsze do odczytania niż ich odpowiedniki w komponencie klasy. Mimo że przy wprowadzaniu aktualizacji stanu niesynchonologicznego oraz przekazywaniu danych między punktami zaczepienia lub efektami wzór zaczepienia zwykle nie jest wystarczający, a rozwiązanie oparte na klasie, takie jak kontrolery reaktywne, zwykle sprawdza się świetnie.
Węzły i kontrolery żądań interfejsu API
Często zdarza się, że używany jest element przykuwający żądanie danych do interfejsu API. Weźmy na przykład ten komponent funkcji React, który wykonuje te czynności:
index.tsx
- Renderowanie tekstu
- Renderowanie odpowiedzi użytkownika
useAPI
- Identyfikator użytkownika + nazwa użytkownika
- Komunikat o błędzie
- 404, gdy dociera do użytkownika 11 (według projektu)
- Błąd przerwania, jeśli pobieranie interfejsu API zostało przerwane
- Wczytywanie wiadomości
- Renderuje przycisk polecenia
- Następny użytkownik: który pobiera interfejs API dla następnego użytkownika.
- Anuluj: anuluje pobieranie interfejsu API i wyświetla błąd.
useApi.tsx
- Definiuje niestandardowy punkt zaczepienia
useApi
- asynchroniczne pobieranie obiektu użytkownika z interfejsu API.
- Emituje:
- Nazwa użytkownika
- Wskazuje, czy pobieranie się wczytuje
- jakiekolwiek komunikaty o błędach.
- Wywołanie zwrotne polegające na przerwaniu pobierania
- Przerywa pobieranie, jeśli urządzenie zostanie odłączone.
- Definiuje niestandardowy punkt zaczepienia
Oto implementacja kontrolera reaktywnego i Lit.
Wnioski:
- Elementy sterujące reagujące na zmiany są najbardziej podobne do niestandardowych haka.
- Przekazywanie danych, których nie można renderować, między funkcjami zwracanymi i efektami
- React używa
useRef
do przekazywania danych międzyuseEffect
iuseCallback
- Lit używa prywatnej właściwości klasy
- React w podstawie naśladuje zachowanie prywatnej właściwości klasy.
- React używa
Poza tym, jeśli naprawdę podoba Ci się składnia komponentu funkcji React z haczykami, ale to samo środowisko bez kompilacji, co Lit, zespół Lit zdecydowanie poleca bibliotekę Haunted.
Dzieci
Gniazdo domyślne
Jeśli elementom HTML nie przypisano atrybutu slot
, są one przypisywane do domyślnego, nienazwanego boksu. W poniższym przykładzie MyApp
wstawi jeden akapit w nazwanym boksie. Drugi akapit zostanie domyślnie umieszczony w boksie bez nazwy.
@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 boksu, wywoływane jest zdarzenie slotchange
. Komponent Lit może powiązać detektor zdarzeń ze zdarzeniem slotchange
. W przykładzie poniżej pierwszy przedział znaleziony w instancji shadowRoot
będzie mieć assignedNodes zarejestrowane w konsoli 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 referencji
Lit i React udostępniają odniesienie do elementu HTMLElement po wywołaniu ich funkcji render
. Warto jednak sprawdzić, jak React i Lit tworzą DOM, który jest później zwracany przez dekorator Lit @query
lub odwołanie do Reacta.
React to funkcjonalny kanał, który tworzy komponenty React, a nie elementy HTMLElement. Ponieważ referencja jest deklarowana przed wyrenderowaniem elementu HTMLElement, jest przydzielany jej fragment pamięci. Dlatego jako wartość początkową źródła odsyłającego widzisz null
, ponieważ element DOM nie został jeszcze utworzony (ani wyrenderowany), tj. useRef(null)
.
Gdy ReactDOM przekształci komponent React w element HTMLElement, szuka w komponencie ReactComponent atrybutu o nazwie ref
. Jeśli to możliwe, ReactDOM umieszcza odniesienie HTMLElement na 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 zakresie ograniczonym do korzeni cienia. Dekorator @query
tworzy getter dla właściwości, który zasadniczo wykonuje operację this.shadowRoot.querySelector
w przypadku ograniczonego korzenia.
Wysyłanie zapytań do wielu elementów
W poniższym przykładzie dekorator @queryAll
zwróci 2 akapity w głównym cieniu 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>
`;
}
}
Zasadniczo @queryAll
tworzy metodę dostępu do paragraphs
, która zwraca wyniki paragraphs
. W języku JavaScript można zadeklarować metodę pobierania w takim samym celu:
get paragraphs() {
return this.renderRoot.querySelectorAll('p');
}
Zapytania dotyczące elementów zmieniających się
Dekorator @queryAsync
lepiej nadaje się do obsługi węzła, który może się zmieniać w zależności od stanu innej właściwości elementu.
W poniższym przykładzie @queryAsync
znajdzie pierwszy element akapitu. Element akapitu będzie jednak renderowany tylko wtedy, gdy renderParagraph
wygeneruje losowo liczbę nieparzystą. Dyrektywa @queryAsync
zwróci obietnicę, która zostanie zrealizowana, gdy dostępny będzie pierwszy akapit.
@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żywanie wywołań zwrotnych, ponieważ stan jest pośredniczony przez React. W przypadku Reacta lepiej jest nie polegać na stanie dostarczanym przez elementy. DOM to po prostu efekt procesu renderowania.
Stan zewnętrzny
Oprócz Lit możesz używać Redux, MobX lub innej biblioteki do zarządzania stanem.
Komponenty Lit są tworzone w zakresie przeglądarki. Oznacza to, że każda biblioteka, która jest dostępna w ramach przeglądarki, jest dostępna dla Lit. Powstało wiele niesamowitych bibliotek, które wykorzystują istniejące systemy zarządzania stanowego w Lit.
Oto seria artykułów firmy Vaadin, w której wyjaśnia ona, jak korzystać z Redux w komponencie Lit.
Zapoznaj się z dokumentem lit-mobx firmy Adobe, aby dowiedzieć się, jak witryna na dużą skalę może wykorzystać możliwości MobX w Lit.
Zapoznaj się też z artykułem Apollo Elements, aby dowiedzieć się, jak programiści wykorzystują GraphQL w swoich komponentach internetowych.
Lit współpracuje z wbudowanymi funkcjami przeglądarki, a w komponencie Lit można używać większości rozwiązań do zarządzania stanem w ramach przeglądarki.
Styl
Shadow DOM
Aby hermetyzować style i DOM w elementach niestandardowych, Lit używa Shadow DOM. Źródło cienia generuje drzewo cienia niezależne od głównego drzewa dokumentu. Oznacza to, że większość stylów jest ograniczona do tego dokumentu. Niektóre style mogą przeciekać przez nie, na przykład kolory i inne style powiązane z czcionkami.
Shadow DOM wprowadza też nowe zagadnienia 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.
*/
}
Style udostępniania
Lit ułatwia udostępnianie stylów między komponentami w postaci CSSTemplateResults
za pomocą tagów szablonów 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
Pierwiastki cienia stanowią pewną wyzwanie dla konwencjonalnych motywów, czyli zwykle z odgórnego podejścia do tagów stylu. Konwencjonalny sposób tworzenia motywów za pomocą komponentów internetowych, które korzystają z Shadow DOM, polega na udostępnieniu interfejsu API stylów za pomocą własnych właściwości CSS. Oto przykład używany w projekcie Material Design:
.mdc-textfield-outline {
border-color: var(--mdc-theme-primary, /* default value */ #...);
}
.mdc-textfield--input {
caret-color: var(--mdc-theme-primary, #...);
}
Następnie użytkownik 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 stylizacja od góry do dołu jest niezbędna, a nie możesz ujawnić stylów, możesz wyłączyć DOM cieniowany, zastępując createRenderRoot
wartością this
, która spowoduje wyrenderowanie szablonu komponentów do samego elementu niestandardowego, a nie do korzenia cieniowanego dołączonego do elementu niestandardowego. W efekcie utracisz hermetyzację stylów, hermetyzację DOM i sloty.
Produkcja
IE 11
Jeśli musisz obsługiwać starsze przeglądarki, takie jak IE 11, musisz załadować niektóre polyfille, które zajmują około 33 KB. Więcej informacji znajdziesz tutaj.
Pakiety warunkowe
Zespół Lit zaleca wyświetlanie 2 różnych pakietów: jednego dla IE 11 i drugiego dla nowoczesnych przeglądarek. Ma to kilka zalet:
- Usługa ES 6 jest szybsza i będzie obsługiwać większość Twoich klientów.
- Transpilacja ES 5 znacznie zwiększa rozmiar pakietu
- Pakiety warunkowe zapewniają to, co najlepsze w obu tych miejscach
- Obsługa przeglądarki IE 11
- Brak korka w nowoczesnych przeglądarkach
Więcej informacji o tworzeniu pakietu wyświetlanego warunkowo znajdziesz w naszej dokumentacji tutaj.