Informacje o tym ćwiczeniu (w Codelabs)
1. Wprowadzenie
Czym 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ść na Lit kilka koncepcji z React, takich jak:
- JSX i Na podstawie szablonu
- Komponenty Elementy wystroju
- Stan i Cykl życia
- Hooks
- Dzieci
- Odwołania
- Stan pośrednictwa
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ść języków HTML, CSS i JavaScript oraz Narzędzi deweloperskich w Chrome.
- Znajomość React
- (Zaawansowane) Aby uzyskać najlepsze rezultaty podczas programowania, pobierz VS Code. Potrzebna będzie też lita wtyczka na potrzeby kodu VS Code i NPM.
2. Oświetlenie vs. reakcja
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, lit-html działa o 8–10% szybciej niż React w najgorszym przypadku i o ponad 50%szybciej w bardziej powszechnych 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 literały szablonów, Lit nie wymaga kompilacji w celu uruchomienia. 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 rozpocząć pracę.
Jeśli chcesz, możesz nadal 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
Jej komponenty bazują na zestawie standardów internetowych nazywanych komponentami sieciowymi. Oznacza to, że utworzenie komponentu w Lit będzie działać zarówno w obecnej, jak i przyszłej platformie. Jeśli reklama ma elementy HTML, i komponenty internetowe.
Jedyne problemy ze interoperacyjnością platformy polegają na tym, że platformy obsługują model DOM w sposób ograniczony. React jest jedną z tych platform, ale ta metoda pozwala uciec w sytuacjach dzięki Refs in React.
Zespół Lit pracuje nad eksperymentalnym projektem @lit-labs/react
, który automatycznie analizuje komponenty Lite i wygeneruje opakowanie React, dzięki czemu nie trzeba będzie używać odesłań.
Dodatkowo dzięki narzędziu Custom Elements Everywhere dowiesz się, 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 języku JavaScript, Lit jest napisany w języku TypeScript, a zespół Lit zaleca też programistom używanie 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 w czasie ich programowania, jak i budowania za pomocą lit-analyzer
i lit-plugin
.
Narzędzia deweloperskie są wbudowane w przeglądarkę
Komponenty Lite 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 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 opracowano z myślą o obsłudze SSR. 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ół ds. Lit planuje wkrótce udostępnić te narzędzia zewnętrznie na GitHubie.
Tymczasem możesz śledzić postępy zespołu Lit tutaj.
Niskie zainteresowanie
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 podobają, nie musisz konwertować całej aplikacji od razu, ponieważ komponenty internetowe działają na innych platformach.
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 treści do komponentów nowej platformy.
Dodatkowo wiele nowoczesnych platform obsługuje dane wyjściowe w komponentach internetowych, co oznacza, że zwykle same mieszczą się w elemencie Lit.
3. Przygotowanie poznawanie Playground
To ćwiczenie z programowania możesz wykonać 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:
To piaskownica kodu, która w całości działa 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';
Cały samouczek możesz zapoznać się z platformą Lit, wykorzystując te punkty kontrolne jako punkty 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 interfejsu Lit Marketplace z wyróżnionymi sekcjami, których użyjesz w tym ćwiczeniu z programowania.
- Selektor plików. Zwróć uwagę na przycisk plusa...
- Edytor plików.
- Podgląd kodu.
- Przycisk ponownego załadowania.
- Przycisk pobierania.
Konfiguracja VS Code (zaawansowane)
Oto zalety tej konfiguracji VS Code:
- Sprawdzanie typu szablonu
- Szablon Intellisense & 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:
- Kliknij 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 programisty i uruchom 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ładowego
package.json
, użyjnpm run dev
- Jeśli używasz przykładowego
4. JSX i Na podstawie szablonu
W tej sekcji poznasz podstawy tworzenia szablonów w Lit.
JSX i Szablony Lit
JSX to rozszerzenie składni języka JavaScript, które umożliwia użytkownikom React łatwe pisanie szablonów w kodzie JavaScript. Szablony Lite służą podobnemu celowi: przedstawiają interfejs komponentu jako funkcję jego stanu.
Składnia podstawowa
W React można wyświetlić w ten sposób świat powitalny JSX:
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 są 2 elementy i uwzględniony element „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
);
Zwróć uwagę, że szablony Lit nie wymagają fragmentu kodu React, by grupować wiele elementów w szablonach.
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 obsługują inne szablony Lit, znane jako TemplateResult
. Możesz na przykład opakować tag name
kursywą (<i>
) i zapakować go w literał szablonu Uwaga. 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
);
Elementy oświetleniowe TemplateResult
mogą akceptować tablice, ciągi znaków, inne elementy 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 rekwizytó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 tym przykładzie zostały zdefiniowane dane wejściowe:
- 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 wykonaj 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
.
Dalej wystę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 przełączania klas.
Na koniec jest konfigurowana właściwość value
w danych wejściowych. W przeciwieństwie do React nie spowoduje to ustawienia elementu wejściowego w trybie tylko do odczytu, ponieważ jest on zgodny z natywną implementacją i zachowaniem danych wejściowych.
Składnia powiązania funkcji Lit prop
html`<my-element ?attribute-name=${booleanVar}>`;
- Prefiks
?
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
- Sprawdza się w przypadku przekazywania złożonych danych, takich jak obiekty, tablice lub klasy
html`<my-element attribute-name=${stringVar}>`;
- Wiąże 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:
- Zapisz słowo „kliknięcie” po kliknięciu danych wejściowych.
- Rejestruj wartość danych wejściowych, gdy użytkownik wpisze znak
W Lit wykonaj 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 Elementy wystroju
W tej sekcji poznasz komponenty i funkcje klasy Lit. W dalszych sekcjach omawiamy stan i elementy przykuwające uwagę.
Akcesoria klasowe LitElement
Litowym odpowiednikiem komponentu klasy React jest LitElement, a pojęcie „właściwości reaktywnych” w Lit. to połączenie rekwizytów i stanu Reacta. 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 powyższym przykładzie widać komponent React, który:
- Renderuje pole
name
- Ustawia wartość domyślną
name
na pusty ciąg znaków (""
) - Przypisuje
name
do użytkownika"Elliott"
Tak to się robi w LitElement
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 języku 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>
Analiza tego, co dzieje się 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 przekształcić atrybut komponentu (czyli ciągi tekstowe) 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>`
}
- Ta funkcja jest wywoływana za każdym razem, gdy dowolna właściwość reaktywna zostanie zmieniona
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
...
}
- Powiąże to nazwę tagu Element HTML z definicją klasy
- Ze względu na standard elementów niestandardowych nazwa tagu musi zawierać łącznik (-).
- Pole
this
w elemencie LitElement odnosi się do wystąpienia 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 ma interpretacji 1:1 komponentu funkcji, ponieważ nie korzysta on z JSX ani procesora wstępnego. Można jednak łatwo utworzyć funkcję, która pobiera właściwości i na ich podstawie renderuje model 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 Lit 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 poznasz stan i cykl życia Lit.
Stan
Pojęcie „właściwości reaktywnych” według Lit to połączenie stanu i rekwizytów Reacta. Zmienione właściwości reaktywne mogą aktywować cykl życia komponentu. Właściwości reaktywne występują w 2 wariantach:
Publiczne usługi 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
- Podobny do rekwizytów i stanu React, ale zmienny
- 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 licencyjny to również
constructor
- Nie trzeba nic przekazywać w ramach superwywołania
- Wywoływane przez (niecałkowicie):
document.createElement
document.innerHTML
new ComponentClass()
- Jeśli na stronie znajduje się nieuaktualniona nazwa tagu, a definicja została wczytana i zarejestrowana w usłudze
@customElement
lubcustomElements.define
- Działa podobnie jak
constructor
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 zwrócić dowolny możliwy do renderowania wynik, np.
TemplateResult
lubstring
itp. - Podobnie jak w przypadku React,
render()
powinna być czystą funkcją - Wyrenderowany zostanie dowolny węzeł zwracany przez
createRenderRoot()
(domyślnieShadowRoot
)
componentDidMount
Funkcja componentDidMount
jest podobna do kombinacji wywołań zwrotnych cyklu życia firstUpdated
i connectedCallback
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 przy pierwszym renderowaniu szablonu komponentu w katalogu głównym komponentu.
- Jest wywoływana tylko wtedy, gdy element jest połączony, np. nie jest wywoływane przez
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 elementy niestandardowe, które są odłączone od DOM, nie są zniszczone, więc można je „połączyć”. wiele razy
firstUpdated
nie zostanie wywołany ponownie
- 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);
}
}
- Odpowiednik w języku angielskim to
updated
(w czasie przeszłym w języku angielskim: „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łączone od DOM, komponent nie zostaje zniszczony.
- 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 niedziałających odwołań, aby przeglądarka mogła wyczyścić pamięć urządzenia
Ć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” i wyświetla godzinę,
- Zegar będzie się aktualizować co sekundę
- Odłączenie powoduje usunięcie interwału wywołującego znacznik
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 zadeklaruj ją jako wewnętrzną właściwość reaktywną przy użyciu @state
, ponieważ użytkownicy komponentu nie będą bezpośrednio ustawiać elementu date
.
// 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ę znacznika.
tick() {
this.date = new Date();
}
Kolejny krok to implementacja componentDidMount
. Lit-analog to znowu mieszanka elementów 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 interwał jest wyczyszczony po usunięciu elementu z drzewa dokumentu, więc jeśli zostanie ponownie podłączony, interwał będzie musiał zaczynać się od nowa. 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 uruchamiał znacznika 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. Hooks
W tej sekcji dowiesz się, jak tłumaczyć koncepcje związane z haczykiem React na język Lit.
Elementy przykuwające uwagę React
Punkty zaczepienia reakcji umożliwiają komponentom funkcji ich „zaczepienie” do nowego stanu. Ma to kilka zalet.
- Upraszczają ponowne wykorzystanie logiki stanowej.
- Pomóż podzielić komponent 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 został rozwiązany w ES2019
- Problemy spowodowane tym, że atrybut
this
nie odnosi się już do komponentu
Reagowanie na elementy przykuwające uwagę w Lit
Jak wspomnieliśmy w sekcji Komponenty rekwizytów. Lit nie oferuje możliwości 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++;
}
}
W jaki sposób Lit rozwiązuje te problemy?
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 odniesienia do elementu niestandardowego- Właściwości klas można teraz tworzyć jako elementy klasy. Upraszcza to implementacje konstruktorowe
Kontrolery reaktywne
Głównymi pojęciami stojącymi za przypadki są w Lit 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.
Kontroler reaktywny to interfejs obiektu, który można połączyć z cyklem aktualizacji hosta kontrolera, takiego jak LitElement.
Cykl życia elementów ReactiveController
i reactiveControllerHost
to:
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>;
}
Gdy stworzysz kontroler reaktywny i dołączysz go do hosta z funkcją addController
, cykl życia kontrolera będzie wywoływany równolegle z cyklem życia hosta. Na przykład zwróć uwagę na przykład zegara z sekcji State & Sekcja 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świetli się komunikat „Hello, World! Jest” i wyświetla godzinę,
- Zegar będzie się aktualizować co sekundę
- Odłączenie powoduje usunięcie interwału wywołującego znacznik
Tworzenie rusztowania 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);
Tworzę kontroler
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 zasobu
connectedCallback
LitElement - Do urządzenia
hostConnected
kontrolera
- Do zasobu
ComponentWillUnmount
- Do zasobu
disconnectedCallback
LitElement - Do urządzenia
hostDisconnected
kontrolera
- Do zasobu
Więcej informacji o przekształcaniu cyklu życia w React na cykl życia Lit znajdziesz w artykułach State & Cykl życia.
Następnie zaimplementuj wywołanie zwrotne hostConnected
i metody tick
, a następnie wyczyść interwał w hostDisconnected
, tak jak w przykładzie w State & Sekcja 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żywać kontrolera zegara, zaimportuj kontroler i zaktualizuj komponent w narzędziu 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żywać kontrolera, musisz utworzyć jego instancję, przesyłając odwołanie do hosta kontrolera (który jest komponentem <my-element>
), a następnie użyć go w metodzie render
.
Aktywowanie 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ę, a host się nie aktualizuje. 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ć!
Bardziej szczegółowe porównanie typowych przypadków użycia z przypadkami użycia elementów zakotwiczonych znajdziesz w sekcji Tematy zaawansowane – elementy przykuwające uwagę.
8. Dzieci
Z tej sekcji dowiesz się, jak używać przedziałów do zarządzania elementami podrzędnymi w Lit.
Automaty Dzieci
Boksy umożliwiają kompozycję, umożliwiając zagnieżdżanie komponentów.
W React dzieci są dziedziczone przez rekwizyty. 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. Zwróć uwagę, że elementy podrzędne nie są dziedziczone w taki sam sposób jak 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 można dodać wiele boksów tak samo, jak dziedziczenie większej liczby rekwizytów.
const MyArticle = (props) => {
return (
<article>
<header>
{props.headerChildren}
</header>
<section>
{props.sectionChildren}
</section>
</article>
);
};
Podobnie dodanie większej liczby elementów <slot>
powoduje utworzenie większej liczby przedziałó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
Gdy do tego przedziału nie przypisano żadnych węzłów, boksy wyświetlają swoje drzewo podrzędne. Przy prognozowaniu węzłów dla przedziału nie będzie ono wyświetlane jako drzewo podrzędne, a zamiast tego wyświetlane będą węzły przewidywane.
@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 przedziałów przez właściwości komponentu. W poniższym przykładzie elementy React są przekazywane do komponentów 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 przedziału (np. <slot>
) ani przedziału z atrybutem name
(np. <slot name="foo">
), który pasuje do atrybutu slot
elementów podrzędnych elementu niestandardowego (np. <div slot="foo">
), taki węzeł nie będzie uwzględniany w prognozie i nie będzie wyświetlany.
9. Odwołania
Czasami programista może potrzebować dostępu do interfejsu API elementu HTMLElement.
W tej sekcji dowiesz się, jak uzyskiwać odwołania do elementów w Lit.
Odniesienia do reakcji
Komponent React jest przetranspilowany na serię wywołań funkcji, które po wywołaniu tworzą wirtualny DOM. Ten wirtualny DOM jest interpretowany przez ReactDOM i renderuje element HTMLElements.
W React odwołania to miejsca w pamięci, które zawierają 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 działania:
- Renderuj puste pole tekstowe i przycisk z tekstem
- Zaznacz dane wejściowe po kliknięciu przycisku
Po początkowym wyrenderowaniu React ustawi element inputRef.current
na wygenerowany HTMLInputElement
atrybut ref
.
Lit „Odnośniki” dzięki @query
Lit znajduje się blisko przeglądarki i ma bardzo małą abstrakcję w stosunku do 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 załą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 zatwierdzi szablon metody render
do źródła elementu my-element
, dekorator @query
umożliwi teraz funkcji inputEl
zwrócenie pierwszego elementu input
znalezionego w głównej części renderowania. Jeśli @query
nie znajdzie podanego elementu, zwróci wartość null
.
Jeśli w katalogu głównym renderowania było wiele elementów input
, @queryAll
zwróci listę węzłów.
10. Stan pośrednictwa
W tej sekcji dowiesz się, jak pośredniczyć w stanach między komponentami w Lit.
Komponenty wielokrotnego użytku
React naśladuje funkcjonalne potoki renderowania z przepływem danych od góry. Rodzice zapewniają dzieciom stan 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 jako etykietą
- 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. Komponent React w powyższym przykładzie 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 powyższym przykładzie komponent Lit wykona te działania:
- Utwórz usługę reaktywną
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, dlatego rzadko widzimy, że komponenty React wysyłają i nasłuchują zdarzenia w taki sam sposób jak komponenty Lit.
Komponenty stanowe
W React można często do zarządzania stanem używać haczyków. Komponent MyCounter
można utworzyć przez ponowne użycie komponentu CounterButton
. Zwróć uwagę, że metoda 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>
);
};
Przykład powyżej wygląda tak:
- 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ę obiektu 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
count
, dodając wartość znajdującą się wdetail.step
zdarzeniaupdate-counter
- Ustawia wartość
step
parametrucounter-button
za pomocą atrybutustep
Bardziej konwencjonalne jest wykorzystywanie w Lit właściwości reaktywnych do przekazywania informacji o zmianach z rodziców na dzieci. Zaleca się również używanie systemu zdarzeń przeglądarki do wyświetlania dymków szczegółów od dołu do góry.
To podejście jest zgodne ze sprawdzonymi metodami i jest zgodne z celem Lit, czyli zapewnieniem obsługi komponentów internetowych na wielu platformach.
11. Styl
Z tej sekcji dowiesz się, jak tworzyć style w języku 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 występują 2 nagłówki ze stylem wbudowanym.
Teraz zaimportuj obramowanie border-color.js
i powiąż je 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 w tym dyrektywę.
styleMap
dyrektywa styleMap
ułatwia korzystanie z 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>
`;
}
}
Przykład powyżej:
- Wyświetla
h1
z obramowaniem 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
. Pole styleMap
ma składnię podobną do składni 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>
`;
}
}
Przykład powyżej:
- Deklaruje literał szablonu otagowany CSS z powiązaniem
- Ustawia kolory 2 elementów
h1
z identyfikatorami
Zalety korzystania z tagu szablonu css
:
- Przeanalizowane raz na klasę lub instancję
- Wdrożenie z myślą o możliwości wielokrotnego wykorzystania modułów
- Możliwość łatwego rozdzielania stylów na osobne pliki
- Zgodność z polyfill w przypadku właściwości niestandardowych CSS
Zwróć też uwagę na tag <style>
w tagu index.html
:
<!-- index.html -->
<style>
h1 {
color: red !important;
}
</style>
Lit określa zakres komponentów ich korzenie. Oznacza to, że style nie będą wyciekać ani znikać. Aby przekazywać style do komponentów, zespół Lit zaleca korzystanie z niestandardowych właściwości CSS, ponieważ pozwalają one korzystać z zakresu stylu Lit.
Tagi stylu
Tagi <style>
możesz też wbudować w szablony. 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łaściwości <link rel="stylesheet">
w szablonie również jest możliwe w przypadku stylów, ale nie jest to też zalecane, ponieważ może spowodować początkowy błysk niesformatowanej treści (FOUC).
12. Tematy zaawansowane (opcjonalnie)
JSX i Na podstawie szablonu
Oświetlenie i Wirtualny DOM
Lit-html nie zawiera konwencjonalnego wirtualnego DOM, który rozróżnia poszczególne węzły. Zamiast tego wykorzystuje funkcje wydajności właściwe ze specyfikacji literału szablonu z tagami w 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 przekazane do funkcji tagu mają ten sam wskaźnik (jak pokazano w drugiej części elementu console.log
). Przeglądarka nie odtwarza nowej tablicy strings
przy każdym wywołaniu funkcji tagu, ponieważ korzysta z tego samego literału szablonu (tzn. w tej samej lokalizacji w AST). Dzięki temu wiązanie, analizowanie i buforowanie szablonu w Lit może korzystać z tych funkcji bez dużego obciążenia środowiska wykonawczego.
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 otagowane literały szablonów działają na zasadzie różnic w kodzie przeglądarki w języku C++.
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 edytory tekstu obsługują je domyślnie, np. Atom i wyróżnianie bloków kodu na GitHubie. Zespół Lit ściśle współpracuje ze społecznością przy utrzymaniu projektów takich jak lit-plugin
– wtyczka VS Code, która dodaje do projektów Lit funkcje podświetlania składni, sprawdzania typów i analizy.
Oświetlenie 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);
w ten sposób:
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 korzysta z otagowanych literałów szablonów, które mogą być uruchamiane w przeglądarce bez transpilacji ani wstępnego przetwarzania danych. 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 Elementy wystroju
Reakcje elementy niestandardowe
Pod gołym niebem LitElement wykorzystuje elementy niestandardowe, by pakować swoje komponenty. Elementy niestandardowe wprowadzają pewne kompromisy między komponentami React, jeśli chodzi o komponenty komponentów (stan i cykl życia omówiono szerzej w sekcji Stan i cykl życia).
Oto niektóre zalety elementów niestandardowych jako systemu komponentów:
- Aplikacja jest natywna w przeglądarce i nie wymaga żadnych narzędzi.
- Zgodność z interfejsem API każdej przeglądarki od
innerHTML
idocument.createElement
doquerySelector
- Zwykle można go używać na różnych platformach.
- Można zarejestrować leniwie za pomocą funkcji
customElements.define
i funkcji „hydrate” DOM
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: choć dostawcy przeglądarek wolą zaniedbywać specyfikacji samodzielnie zamykających się tagów, dlatego nowsze specyfikacje zwykle nie zawierają tego tagu.
- Wprowadza dodatkowy węzeł do drzewa DOM, który może powodować problemy z układem
- Musi być zarejestrowana za pomocą JavaScriptu
Lit postawił na elementy niestandardowe nad systemem elementów dostosowanych do indywidualnych potrzeb, ponieważ elementy niestandardowe są wbudowane w przeglądarkę. Zespół Lit uważa, że korzyści związane z międzyramami są wyższe niż korzyści zapewniane przez warstwę abstrakcji komponentów. Dzięki wysiłkom zespołu Lit w dziedzinie „lit-ssr” udało się rozwiązać główne problemy z rejestracją JavaScriptu. 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
Typowe nieporozumienie w przypadku elementów niestandardowych stanowi, że dane można przekazywać tylko jako ciągi znaków. To błędne przekonanie prawdopodobnie wynika z faktu, że atrybuty elementów mogą być zapisywane 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>`;
}
}
Zdefiniowano podstawową właściwość reaktywną num
, która konwertuje ciąg znaków atrybutu na number
. Potem złożona struktura danych jest wprowadzana za pomocą funkcji attribute:false
, która wyłącza 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 w rodzaju rekwizytów, ponieważ są to te same właściwości klasowe.
shouldComponentUpdate
- Odpowiednik licencyjny:
shouldUpdate
- Wywoływane przy pierwszym renderowaniu w odróżnieniu od React
- Działa podobnie jak
shouldComponentUpdate
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 przedrender
- Zmiany we właściwościach reaktywnych w zadaniu
willUpdate
nie powodują ponownego wywołania cyklu aktualizacji - Dobre miejsce do obliczania wartości właściwości, które są zależne 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 SSR, więc nie zaleca się uzyskiwania dostępu do DOM.
update
- Połączenie zakończone po
willUpdate
- W przeciwieństwie do funkcji
getSnapshotBeforeUpdate
funkcjaupdate
jest wywoływana przedrender
- Zmiany właściwości reaktywnych w
update
nie powodują ponownego wywołania cyklu aktualizacji przed wywołaniem funkcjisuper.update
- Dobre miejsce na przechwytywanie informacji z DOM otaczającego komponent, zanim renderowane dane wyjściowe zostaną przypisane do DOM.
- Ta metoda nie jest wywoływana na serwerze w 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ływana po przeniesieniu komponentu do nowego dokumentu, np. z HTMLTemplateElement
użytkownika documentFragment
do głównego document
. To wywołanie zwrotne wchodzi też w skład specyfikacji elementów niestandardowych i powinno być używane 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 są elementami klas, które możesz wywoływać, zastąpić lub oczekiwać, aby ułatwić manipulowanie procesem cyklu życia.
updateComplete
Jest to Promise
, który kończy się po zaktualizowaniu 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
. Zdarza się to często, gdy komponent renderuje komponent podrzędny, a jego 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, 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 aktualizacji składa się z 3 części:
- Przed aktualizacją
- Aktualizuj
- Po aktualizacji
Przed aktualizacją
Po requestUpdate
oczekuje na zaplanowaną aktualizację.
Aktualizuj
Po aktualizacji
Hooks
Dlaczego się przykuwa
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 zaczepami są zwykle znacznie prostsze i czytelniejsze niż ich odpowiedniki w klasach. Mimo że przy wprowadzaniu aktualizacji stanu niesynchonologicznego oraz przekazywaniu danych między punktami zaczepienia lub efektami wzorzec hooków zwykle nie jest wystarczający, a rozwiązanie oparte na klasie, takie jak kontrolery reaktywne, zwykle sprawdza się świetnie.
Punkty zaczepienia żądań do interfejsu API kontrolery
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
- Renderuje tekst
- Renderuje odpowiedź
useAPI
- User ID + nazwa użytkownika
- Komunikat o błędzie
- Błąd 404 po dotarciu do użytkownika 11 (z założenia)
- Przerwij błąd, 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: przerywa pobieranie interfejsu API i wyświetla błąd.
useApi.tsx
- Definiuje niestandardowy punkt zaczepienia
useApi
- Czy asynchroniczne pobranie obiektu użytkownika z interfejsu API
- Emisje:
- Nazwa użytkownika
- Wskazuje, czy pobieranie się wczytuje
- wszelkie komunikaty o błędach.
- Wywołanie zwrotne przerywające pobieranie
- Przerywa pobieranie w przypadku odłączenia
- Definiuje niestandardowy punkt zaczepienia
Oto implementacja kontrolera Lit + Reactive.
Wnioski:
- Kontrolery reaktywne działają jak niestandardowe haczyki
- Przekazywanie danych, których nie można renderować, między wywołaniami zwrotnymi a efektami
- React używa parametru
useRef
do przekazywania danych międzyuseEffect
auseCallback
- Lit używa właściwości klasy prywatnej
- React naśladuje zasadniczo zachowanie właściwości klasy prywatnej
- React używa parametru
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>
`;
}
}
Odwołania
Generowanie plików referencyjnych
Lit i React udostępniają odniesienie do elementu HTMLElement po wywołaniu ich funkcji render
. Warto jednak przyjrzeć się, jak React i Lit tworzą DOM, który jest później zwracany przez dekoratora Lit @query
lub odwołanie do React.
React to funkcjonalny potok, który tworzy komponenty React, a nie HTMLElement. Odwołanie jest deklarowane przed wyrenderowaniem elementu HTMLElement, dlatego przydzielona jest przestrzeń w 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 przekonwertuje komponent React w HTMLElement, szuka w nim atrybutu o nazwie ref
. Jeśli to możliwe, ReactDOM umieszcza odniesienie HTMLElement na ref.current
.
LitElement korzysta z funkcji tagu szablonu html
z lit-html do utworzenia elementu szablonu. LitElement oznacza zawartość szablonu po wyrenderowaniu w modelu shadow DOM elementu niestandardowego. Shadow DOM to drzewo DOM o zakresie ograniczonym do korzeni cienia. Dekorator @query
tworzy następnie metodę pobierania dla właściwości, która zasadniczo wykonuje wywołanie this.shadowRoot.querySelector
w katalogu głównym zakresu.
Zapytanie obejmujące wiele 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 funkcja @queryAll
tworzy metodę pobierającą dla funkcji paragraphs
, która zwraca wyniki funkcji this.shadowRoot.querySelectorAll()
. W języku JavaScript można zadeklarować metodę pobierania w takim samym celu:
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 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średnictwa
W React można używać wywołań zwrotnych, ponieważ stan jest zapośredniczony przez samą React. W reakcji najlepiej nie polegać na stanie dostarczanym przez elementy. DOM jest po prostu efektem procesu renderowania.
Stan zewnętrzny
Razem z Lit można korzystać z Redux, MobX lub dowolnej innej biblioteki zarządzania stanowego.
Komponenty Lit są tworzone w zakresie przeglądarki. Dlatego każda biblioteka, która istnieje też w zakresie 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 autorstwa Vaadina, w której wyjaśnia, jak wykorzystać Redux w komponencie lite.
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 natywnymi funkcjami przeglądarki i większością rozwiązań do zarządzania stanem w zakresie przeglądarki można używać w komponencie Lit.
Styl
Shadow DOM
Aby natywnie hermetyzować style i DOM w elemencie niestandardowym, Lit używa modelu 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 koncepcje 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 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
Pierwiastki cienia stanowią pewną wyzwanie dla konwencjonalnych motywów, czyli zwykle z odgórnego podejścia do tagów stylu. Tradycyjnym sposobem na radzenia sobie z tworzeniem motywów za pomocą komponentów sieciowych, które korzystają z modelu Shadow DOM, jest udostępnienie interfejsu API stylu za pomocą właściwości niestandardowych 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 musisz zastosować schematy z góry i nie masz możliwości wyświetlenia stylów, zawsze możesz wyłączyć model Shadow DOM, zastępując createRenderRoot
tak, aby zwracał wartość this
, co spowoduje wyrenderowanie komponentów. do samego elementu niestandardowego, a nie do poziomu głównego cienia dołączonego do elementu niestandardowego. Utracisz w ten sposób: kodowanie stylu, herbatę DOM i przedziały.
Produkcja
IE 11
Jeśli potrzebna jest obsługa starszych przeglądarek, takich jak IE 11, trzeba będzie załadować kilka elementów polyfill, które wrócą do rozmiaru ok. 33 KB. Więcej informacji znajdziesz tutaj.
Pakiety warunkowe
Zespół Lit zaleca serwowanie 2 różnych pakietów – jednego dla przeglądarki IE 11 i jednego dla nowoczesnych przeglądarek. Ma to kilka zalet:
- Obsługa ES 6 trwa krócej i zapewnia obsługę większości 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 na temat tworzenia pakietu obsługiwanego warunkowo znajdziesz na stronie z dokumentacją.