Lit for React Developers

Lit for React Developers

Informacje o tym ćwiczeniu (w Codelabs)

subjectOstatnia aktualizacja: cze 25, 2021
account_circleAutorzy: Elliott Marquez & Brian Vann

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.

Wykres słupkowy przedstawiający zminimalizowany i skompresowany rozmiar pakietu w KB. Pasek Lit to 5 KB, a model React + React DOM – 42,2 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.

pogrupowany wykres słupkowy przedstawiający porównanie skuteczności w milisekundach (im więcej, tym lepiej)

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.

Zrzut ekranu IDE z sprawdzaniem niewłaściwego typu wartości logicznej wyrażonej w postaci liczby

Zrzut ekranu z IDE zawierającym inteligentne sugestie

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.

obraz narzędzi deweloperskich w Chrome pokazujący wartość $0 zwraca wartość <mwc-textfield>, $0.value zwraca hello world, $0.outlined zwraca wartość true, a {$0} pokazuje rozwinięcie właściwości

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

Pasek na karcie wyboru plików ma etykietę Sekcja 1, sekcja edytowania kodu jako sekcja 2, podgląd wyników jako sekcja 3, a przycisk ponownego załadowania podglądu – sekcja 4.

Zrzut ekranu interfejsu Lit Marketplace z wyróżnionymi sekcjami, których użyjesz w tym ćwiczeniu z programowania.

  1. Selektor plików. Zwróć uwagę na przycisk plusa...
  2. Edytor plików.
  3. Podgląd kodu.
  4. Przycisk ponownego załadowania.
  5. 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).
  • 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żyj npm run dev

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 jako disabled="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 lub customElements.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 lub string itp.
  • Podobnie jak w przypadku React, render() powinna być czystą funkcją
  • Wyrenderowany zostanie dowolny węzeł zwracany przez createRenderRoot() (domyślnie ShadowRoot)

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 obiekcie firstUpdated 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 interfejsie willUpdate.

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 przed firstUpdated, 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 funkcja disconnectedCallback 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 elementem this
  • 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
  • ComponentWillUnmount
    • Do zasobu disconnectedCallback LitElement
    • Do urządzenia hostDisconnected kontrolera

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 dla HTMLInputElement).
  • 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 argumentem props.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>&Sigma;: {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ędzia addToCounter do aktualizowania wartości count do step 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>&Sigma; ${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ę w detail.step zdarzenia update-counter
  • Ustawia wartość step parametru counter-button za pomocą atrybutu step

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.

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 i document.createElement do querySelector
  • 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 funkcja willUpdate jest wywoływana przed render
  • 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 funkcja update jest wywoływana przed render
  • Zmiany właściwości reaktywnych w update nie powodują ponownego wywołania cyklu aktualizacji przed wywołaniem funkcji super.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ą

Skierowany graf acykliczny węzłów z nazwami wywołań zwrotnych. do konstruktora requestUpdate. @property do Ustawienia usługi. addressChangedCallback na konstruktora właściwości. Ustawienia usługi na hasChanged. Zmieniono na requestUpdate. requestUpdate wskazuje następny wykres cyklu życia aktualizacji.

Po requestUpdate oczekuje na zaplanowaną aktualizację.

Aktualizuj

Skierowany graf acykliczny węzłów z nazwami wywołań zwrotnych. Strzałka z poprzedniego obrazu punktów cyklu życia przed aktualizacją do wykonaniaUpdate. Wykonaj aktualizację, aby ją zaktualizować. powinno być aktualizowane przy użyciu punktów „complete update if false” (jeśli ma wartość false), jak i willUpdate. willUpdate. zaktualizowany zarówno do renderowania, jak i do kolejnego wykresu cyklu życia po aktualizacji. wskazuje także następny wykres cyklu życia zmian wprowadzonych po aktualizacji.

Po aktualizacji

Skierowany graf acykliczny węzłów z nazwami wywołań zwrotnych. Strzałka od poprzedniego obrazu pokazującego cykl życia aktualizacji do pola firstUpdated. Pierwsza aktualizacja: zaktualizowana. Zaktualizowano do updateComplete.

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

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ędzy useEffect a useCallback
    • Lit używa właściwości klasy prywatnej
    • React naśladuje zasadniczo zachowanie właściwości klasy prywatnej

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ą.