Od komponentu internetowego do elementu Lit

1. Wprowadzenie

Ostatnia aktualizacja: 10.08.2021

Komponenty internetowe

Komponenty internetowe to zestaw interfejsów API platform internetowych, które umożliwiają tworzenie nowych niestandardowych tagów HTML wielokrotnego użytku, które można stosować na stronach internetowych i w aplikacjach internetowych. Niestandardowe komponenty i widżety oparte na standardach Web Komponent działają w nowoczesnych przeglądarkach i mogą być używane z każdą biblioteką lub platformą JavaScript współpracującą z HTML.

Czym jest lista

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

Lit udostępnia interfejsy API, które upraszczają typowe zadania związane z komponentami internetowymi, takie jak zarządzanie właściwościami i atrybutami czy renderowanie.

Czego się nauczysz

  • Czym jest komponent sieciowy
  • Komponenty sieciowe
  • Jak utworzyć komponent internetowy
  • Czym są lit-html i LitElement
  • Co Lit robi nad komponentem sieciowym

Co utworzysz

  • Wanilia z kciukiem w górę / w dół Komponent sieciowy
  • Komponent sieciowy „Lubię” / w dół

Czego potrzebujesz

  • Każda zaktualizowana nowoczesna przeglądarka (Chrome, Safari, Firefox, Chromium Edge). Komponenty sieciowe działają we wszystkich nowoczesnych przeglądarkach, a elementy polyfill są dostępne dla przeglądarki Microsoft Internet Explorer 11 i Microsoft Edge bez Chrome.
  • Znajomość języków HTML, CSS i JavaScript oraz Narzędzi deweloperskich w Chrome.

2. Przygotowanie poznawanie Playground

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://unpkg.com/lit?module';

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 kart 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
  • 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 serve

3. Definiowanie elementu niestandardowego

Elementy niestandardowe

Komponenty internetowe to zbiór 4 natywnych internetowych interfejsów API. Są to:

  • Moduły ES
  • Elementy niestandardowe
  • Shadow DOM
  • Szablony HTML

Znasz już specyfikację modułów ES, co pozwala tworzyć moduły JavaScriptu z importami i eksportami, które są ładowane na stronie za pomocą <script type="module">.

Definiowanie elementu niestandardowego

Specyfikacja elementów niestandardowych pozwala użytkownikom definiować własne elementy HTML przy użyciu JavaScriptu. Nazwy muszą zawierać łącznik (-), aby odróżnić je od elementów natywnych przeglądarki. Wyczyść plik index.js i zdefiniuj klasę elementu niestandardowego:

index.js

class RatingElement extends HTMLElement {}

customElements.define('rating-element', RatingElement);

Element niestandardowy jest definiowany przez powiązanie klasy, która rozszerza HTMLElement, z nazwą tagu z łącznikiem. Wywołanie customElements.define informuje przeglądarkę, że ma powiązać klasę RatingElement z parametrem tagName ‘rating-element'. Oznacza to, że każdy element o nazwie <rating-element> w dokumencie będzie powiązany z tą klasą.

Umieść <rating-element> w treści dokumentu i sprawdź, co się wyrenderuje.

index.html

<body>
 <rating-element></rating-element>
</body>

Teraz widzisz, że nic nie zostało wyrenderowane. Można się było tego spodziewać, ponieważ przeglądarka nie otrzymała od Ciebie instrukcji renderowania <rating-element>. Aby sprawdzić, czy definicja elementu niestandardowego jest prawidłowa, kliknij <rating-element> w Narzędziach deweloperskich w Chrome selektorem elementów, a w konsoli – wywołaniem:

$0.constructor

Powinien wyświetlić się:

class RatingElement extends HTMLElement {}

Cykl życia elementu niestandardowego

Elementy niestandardowe mają zestaw haczyków związanych z cyklem życia. Są to:

  • constructor
  • connectedCallback
  • disconnectedCallback
  • attributeChangedCallback
  • adoptedCallback

Funkcja constructor jest wywoływana przy pierwszym tworzeniu elementu, np. przez wywołanie document.createElement(‘rating-element') lub new RatingElement(). Konstruktor to dobre miejsce do skonfigurowania elementu, ale manipulowanie DOM w konstruktorze na potrzeby „uruchamiania” jest zwykle złym zamiarem kwestii związanych z wydajnością.

Interfejs connectedCallback jest wywoływany, gdy element niestandardowy jest dołączony do DOM. Zwykle jest to miejsce, w którym następuje początkowe manipulacje DOM.

Funkcja disconnectedCallback jest wywoływana po usunięciu elementu niestandardowego z DOM.

Pole attributeChangedCallback(attrName, oldValue, newValue) jest wywoływane po zmianie dowolnego z atrybutów określonych przez użytkownika.

Funkcja adoptedCallback jest wywoływana, gdy element niestandardowy zostanie przejęty z innego elementu documentFragment do głównego dokumentu za pomocą funkcji adoptNode, np. w HTMLTemplateElement.

Renderowanie DOM

Teraz wróć do elementu niestandardowego i powiąż z nim jakiś DOM. Ustaw treść elementu, gdy zostanie dołączona do modelu DOM:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   this.innerHTML = `
     <style>
       rating-element {
         display: inline-flex;
         align-items: center;
       }
       rating-element button {
         background: transparent;
         border: none;
         cursor: pointer;
       }
     </style>
     <button class="thumb_down" >
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
     </button>
     <span class="rating">${this.rating}</span>
     <button class="thumb_up">
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
     </button>
   `;
 }
}

customElements.define('rating-element', RatingElement);

W constructor przechowujesz w elemencie właściwość instancji o nazwie rating. W connectedCallback dodajesz elementy podrzędne DOM do elementu <rating-element>, aby wyświetlać bieżącą ocenę wraz z przyciskami kciuka w górę i w dół.

4. Shadow DOM

Dlaczego warto korzystać z modelu Shadow DOM?

W poprzednim kroku zauważysz, że selektory we wstawionym tagu stylu wybierają dowolny element oceny na stronie oraz dowolny przycisk. Może to spowodować wyciek stylów z elementu i wybranie innych węzłów, których nie chcesz mieć stylu. Poza tym style spoza tego elementu niestandardowego mogą w niezamierzony sposób określać styl węzłów wewnątrz elementu niestandardowego. Możesz na przykład umieścić tag stylu w nagłówku głównego dokumentu:

index.html

<!DOCTYPE html>
<html>
 <head>
   <script src="./index.js" type="module"></script>
   <style>
     span {
       border: 1px solid red;
     }
   </style>
 </head>
 <body>
   <rating-element></rating-element>
 </body>
</html>

Dane wyjściowe powinny mieć czerwone obramowanie wokół rozpiętości oceny. To banalny przypadek, ale brak enkapsulacji DOM może powodować większe problemy w bardziej złożonych zastosowaniach. W tym przypadku do gry wkracza model Shadow DOM.

Przypinanie korzeni cienia

Dołącz pierwiastek cienia do elementu i wyrenderuj DOM wewnątrz tego pierwiastka:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   const shadowRoot = this.attachShadow({mode: 'open'});

   shadowRoot.innerHTML = `
     <style>
       :host {
         display: inline-flex;
         align-items: center;
       }
       button {
         background: transparent;
         border: none;
         cursor: pointer;
       }
     </style>
     <button class="thumb_down" >
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
     </button>
     <span class="rating">${this.rating}</span>
     <button class="thumb_up">
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
     </button>
   `;
 }
}

customElements.define('rating-element', RatingElement);

Po odświeżeniu strony zauważysz, że style w dokumencie głównym nie mogą już wybierać węzłów w katalogu głównym cienia.

Jak to zrobiłeś? W elemencie connectedCallback nazwa this.attachShadow powoduje dołączenie pierwiastka cienia do elementu. Tryb open oznacza, że cień jest podlegającą sprawdzeniu i udostępnia pierwiastek cienia także przez this.shadowRoot. Możesz też sprawdzić komponent sieciowy w inspektorze Chrome:

Drzewo DOM w inspektorze Chrome. Istnieje element <rating-element> z#shadow-root (otwartym) jako elementem podrzędnym, a modelem DOM wewnątrz tego shadowroota.

Powinien być widoczny rozwijany katalog główny, w którym znajduje się treść. Wszystko w obrębie korzenia cienia jest nazywane modelem Shadow DOM. Jeśli wybierzesz element oceny w Narzędziach deweloperskich w Chrome i wywołasz funkcję $0.children, zauważysz, że nie zawiera on żadnych elementów podrzędnych. Dzieje się tak, ponieważ model Shadow DOM nie jest uważany za część tego samego drzewa DOM co bezpośrednie elementy podrzędne, ale raczej za drzewo cienia.

Light DOM.

Eksperyment: dodaj węzeł jako bezpośredni element podrzędny instancji <rating-element>:

index.html

<rating-element>
 <div>
   This is the light DOM!
 </div>
</rating-element>

Odśwież stronę. Przekonasz się, że nowy węzeł DOM w elemencie Light DOM tego elementu niestandardowego nie jest widoczny. Wynika to z faktu, że model Shadow DOM ma funkcje kontrolujące sposób rzutowania węzłów Light DOM na obszar cieni za pomocą elementów <slot>.

5. Szablony HTML

Dlaczego szablony

Używanie właściwości innerHTML i ciągów literału szablonu bez oczyszczania może spowodować problemy z bezpieczeństwem wstrzykiwania skryptów. Wcześniej niektóre metody obejmowały używanie elementów DocumentFragment, ale wiązały się z nimi też inne problemy, np. wczytywanie obrazów i uruchamianie skryptów, gdy szablony są zdefiniowane, a także utrudniające ponowne wykorzystanie. Dlatego do akcji wkracza element <template>. Szablony zapewniają bezczynny DOM, wysoce wydajną metodę klonowania węzłów oraz szablony wielokrotnego użytku.

Korzystanie z szablonów

Następnie przenieś komponent, tak aby używał szablonów HTML:

index.html

<body>
 <template id="rating-element-template">
   <style>
     :host {
       display: inline-flex;
       align-items: center;
     }
     button {
       background: transparent;
       border: none;
       cursor: pointer;
     }
   </style>
   <button class="thumb_down" >
     <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
   </button>
   <span class="rating"></span>
   <button class="thumb_up">
     <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
   </button>
 </template>

 <rating-element>
   <div>
     This is the light DOM!
   </div>
 </rating-element>
</body>

W tym przypadku treść DOM została przeniesiona do tagu szablonu w modelu DOM dokumentu. Teraz zastosuj refaktoryzację definicji elementu niestandardowego:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   const shadowRoot = this.attachShadow({mode: 'open'});
   const templateContent = document.getElementById('rating-element-template').content;
   const clonedContent = templateContent.cloneNode(true);
   shadowRoot.appendChild(clonedContent);

   this.shadowRoot.querySelector('.rating').innerText = this.rating;
 }
}

customElements.define('rating-element', RatingElement);

Aby użyć tego elementu szablonu, wykonaj zapytanie na szablonie, pobierz jego zawartość i sklonuj te węzły za pomocą funkcji templateContent.cloneNode, w której argument true wykonuje głęboką klon. Następnie inicjujesz obiekt DOM z użyciem tych danych.

Gratulacje. Komponent sieciowy jest już dostępny. Niestety nie ma jeszcze żadnych funkcji, więc w następnej kolejności dodaj trochę funkcji.

6. Dodawanie funkcji

Powiązania usług

Obecnie jedynym sposobem określenia oceny w elemencie oceny jest skonstruowanie tego elementu, ustawienie właściwości rating w obiekcie, a następnie umieszczenie jej na stronie. Niestety, nie tak zazwyczaj działają natywne elementy HTML. Natywne elementy HTML zwykle są aktualizowane po zmianie właściwości i atrybutów.

Aby element niestandardowy aktualizował widok po zmianie właściwości rating, dodaj te wiersze:

index.js

constructor() {
  super();
  this._rating = 0;
}

set rating(value) {
  this._rating = value;
  if (!this.shadowRoot) {
    return;
  }

  const ratingEl = this.shadowRoot.querySelector('.rating');
  if (ratingEl) {
    ratingEl.innerText = this._rating;
  }
}

get rating() {
  return this._rating;
}

Dodajesz zmienną ustawiającą i metodę pobierania dla właściwości „ocena”, a następnie aktualizujesz tekst elementu „ocena”, jeśli jest dostępny. Oznacza to, że jeśli ustawisz właściwość „Rating” elementu, widok zostanie zaktualizowany. Wykonaj szybki test w konsoli Narzędzi deweloperskich.

Powiązania atrybutów

Teraz zaktualizuj widok, gdy atrybut się zmieni. jest to podobne do danych wejściowych aktualizujących swój widok podczas konfigurowania <input value="newValue">. Na szczęście cykl życia komponentu internetowego obejmuje też attributeChangedCallback. Zaktualizuj ocenę, dodając te wiersze:

index.js

static get observedAttributes() {
 return ['rating'];
}

attributeChangedCallback(attributeName, oldValue, newValue) {
 if (attributeName === 'rating') {
   const newRating = Number(newValue);
   this.rating = newRating;
 }
}

Aby można było aktywować regułę attributeChangedCallback, musisz ustawić statyczną metodę pobierania dla RatingElement.observedAttributes which defines the attributes to be observed for changes. Następnie określasz ocenę deklaratywnie w DOM. Wypróbuj tę funkcję:

index.html

<rating-element rating="5"></rating-element>

Ocena powinna być teraz deklaratywnie aktualizowana.

Funkcje przycisku

Teraz brakuje tylko przycisków. Działanie tego komponentu powinno umożliwiać użytkownikowi wystawienie pojedynczej oceny pozytywnej lub negatywnej oraz przedstawienie mu wizualnej opinii. Możesz wdrożyć to rozwiązanie z niektórymi detektorami zdarzeń i właściwością odbicia. Najpierw jednak zaktualizuj style, aby przekazać wizualną informację zwrotną, dołączając do nich te wiersze:

index.html

<style>
...

 :host([vote=up]) .thumb_up {
   fill: green;
 }
  :host([vote=down]) .thumb_down {
   fill: red;
 }
</style>

W modelu Shadow DOM selektor :host odnosi się do węzła lub elementu niestandardowego, do którego dołączony jest element główny cienia. W tym przypadku, gdy atrybut vote ma wartość "up", przycisk kciuka w górę zmieni kolor na zielony, ale gdy vote ma wartość "down", then it will turn the thumb-down button red. Teraz zaimplementuj tę logikę, tworząc właściwości / atrybut odzwierciedlający obiekt vote, podobnie jak w przypadku implementacji rating. Zacznij od metody ustawiania i pobierania właściwości:

index.js

constructor() {
  super();
  this._rating = 0;
  this._vote = null;
}

set vote(newValue) {
  const oldValue = this._vote;
  if (newValue === oldValue) {
    return;
  }
  if (newValue === 'up') {
    if (oldValue === 'down') {
      this.rating += 2;
    } else {
      this.rating += 1;
    }
  } else if (newValue === 'down') {
    if (oldValue === 'up') {
      this.rating -= 2;
    } else {
      this.rating -= 1;
    }
  }
  this._vote = newValue;
  this.setAttribute('vote', newValue);
}

get vote() {
  return this._vote;
}

Inicjujesz właściwość instancji _vote za pomocą parametru null w elemencie constructor, a w ustawieniu ustalasz, czy nowa wartość jest inna. Jeśli tak, dostosujesz ocenę i, co najważniejsze, odzwierciedlaj atrybut vote hostowi za pomocą this.setAttribute.

Następnie skonfiguruj wiązanie atrybutu:

index.js

static get observedAttributes() {
  return ['rating', 'vote'];
}

attributeChangedCallback(attributeName, oldValue, newValue) {
  if (attributeName === 'rating') {
    const newRating = Number(newValue);

    this.rating = newRating;
  } else if (attributeName === 'vote') {
    this.vote = newValue;
  }
}

Również w tym przypadku chodzi o ten sam proces z wiązaniem atrybutu rating. dodajesz właściwość vote do elementu observedAttributes i ustawiasz właściwość vote w elemencie attributeChangedCallback. Na koniec dodaj odbiorniki zdarzeń kliknięcia, aby zapewnić funkcjonalność przycisków.

index.js

constructor() {
 super();
 this._rating = 0;
 this._vote = null;
 this._boundOnUpClick = this._onUpClick.bind(this);
 this._boundOnDownClick = this._onDownClick.bind(this);
}

connectedCallback() {
  ...
  this.shadowRoot.querySelector('.thumb_up')
    .addEventListener('click', this._boundOnUpClick);
  this.shadowRoot.querySelector('.thumb_down')
    .addEventListener('click', this._boundOnDownClick);
}

disconnectedCallback() {
  this.shadowRoot.querySelector('.thumb_up')
    .removeEventListener('click', this._boundOnUpClick);
  this.shadowRoot.querySelector('.thumb_down')
    .removeEventListener('click', this._boundOnDownClick);
}

_onUpClick() {
  this.vote = 'up';
}

_onDownClick() {
  this.vote = 'down';
}

W obiekcie constructor powiążesz część detektorów kliknięć z elementem i zachowasz odwołania. W connectedCallback nasłuchujesz zdarzeń kliknięcia przycisków. Wyczyścisz te detektory kliknięć w usłudze disconnectedCallback, a w samych nich prawidłowo ustawisz vote.

Gratulujemy! Masz teraz w pełni funkcjonalny komponent sieciowy. spróbuj kliknąć jakieś przyciski! Problem polega teraz na tym, że mój plik JS ma teraz 96 wierszy, plik HTML 43 wiersze, a kod jest dość szczegółowy i musi mieć znaczenie w przypadku tak prostego komponentu. Właśnie tu do akcji wkracza projekt Google Lit.

7. Lit-html

Punkt kontroli kodu

Dlaczego lit-html

Przede wszystkim tag <template> jest przydatny i wydajny, ale nie jest połączony z logiką komponentu, co utrudnia jego rozpowszechnianie wraz z pozostałą logiką. Poza tym sposób wykorzystania elementów szablonu jest nieodłączną częścią kodu, który w wielu przypadkach prowadzi do mniej zrozumiałego kodu w porównaniu z deklaratywnymi wzorcami kodowania.

Tu do akcji wkracza lit-html. Lit HTML to system renderowania języka Lit, który umożliwia pisanie szablonów HTML w języku JavaScript, a następnie skuteczne renderowanie i ponowne renderowanie tych szablonów wraz z danymi w celu utworzenia i aktualizowania modelu DOM. Jest podobna do popularnych bibliotek JSX i VDOM, ale w wielu przypadkach działa natywnie w przeglądarce, a w wielu przypadkach znacznie wydajniej.

Korzystanie z literatu HTML

Następnie przenieś natywny komponent internetowy rating-element, aby używał szablonu lit korzystającego z literatów szablonu otagowanych, czyli funkcji, które wykorzystują ciągi szablonów jako argumenty o specjalnej składni. Następnie Lit wykorzystuje wbudowane elementy szablonu, aby zapewnić szybkie renderowanie i zapewnić bezpieczeństwo. Najpierw przenieś plik <template> w przeglądarce index.html do szablonu Lit, dodając metodę render() do komponentu webcomponent:

plik index.js

// Dont forget to import from Lit!
import {render, html} from 'lit';

class RatingElement extends HTMLElement {
  ...
  render() {
    if (!this.shadowRoot) {
      return;
    }

    const template = html`
      <style>
        :host {
          display: inline-flex;
          align-items: center;
        }
        button {
          background: transparent;
          border: none;
          cursor: pointer;
        }

       :host([vote=up]) .thumb_up {
         fill: green;
       }

       :host([vote=down]) .thumb_down {
         fill: red;
       }
      </style>
      <button class="thumb_down">
        <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
      </button>
      <span class="rating">${this.rating}</span>
      <button class="thumb_up">
        <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
      </button>`;

    render(template, this.shadowRoot);
  }
}

Możesz też usunąć szablon z index.html. W tej metodzie renderowania definiujesz zmienną o nazwie template i wywołujesz funkcję literału szablonu otagowaną html. Zauważysz też, że proste wiązanie danych zostało wykonane w elemencie span.rating przy użyciu składni interpolacji literału szablonu ${...}. Oznacza to, że w końcu nie będzie już konieczne bezterminowe aktualizowanie tego węzła. Dodatkowo wywołasz metodę podświetlenia render, która synchronicznie renderuje szablon w głównym cieniu.

Migracja do składni deklaratywnej

Teraz gdy element <template> został już usunięty, zrefaktoryzuj kod, aby zamiast tego wywoływać nowo zdefiniowaną metodę render. Możesz zacząć od powiązania detektora zdarzeń Lit, aby wyczyścić kod detektora:

index.js

<button
    class="thumb_down"
    @click=${() => {this.vote = 'down'}}>
...
<button
    class="thumb_up"
    @click=${() => {this.vote = 'up'}}>

Szablony Lite mogą dodać detektor zdarzeń do węzła ze składnią powiązania @EVENT_NAME. W takim przypadku właściwość vote jest aktualizowana po każdym kliknięciu tych przycisków.

Następnie wyczyść kod inicjowania detektora zdarzeń w constructor oraz connectedCallback i disconnectedCallback:

plik index.js

constructor() {
  super();
  this._rating = 0;
  this._vote = null;
}

connectedCallback() {
  this.attachShadow({mode: 'open'});
  this.render();
}

// remove disonnectedCallback and _onUpClick and _onDownClick

Udało Ci się usunąć logikę detektora kliknięć ze wszystkich 3 wywołań zwrotnych, a nawet całkowicie usunąć disconnectedCallback. Udało się też usunąć z connectedCallback cały kod inicjowania DOM, co sprawiło, że interfejs wyglądał jeszcze bardziej elegancki. Oznacza to również, że możesz pozbyć się metod detektora _onUpClick i _onDownClick.

Na koniec zaktualizuj metody ustawiania właściwości tak, aby korzystały z metody render, co umożliwi aktualizowanie elementu DOM, gdy zmienią się właściwości lub atrybuty:

index.js

set rating(value) {
  this._rating = value;
  this.render();
}

...

set vote(newValue) {
  const oldValue = this._vote;
  if (newValue === oldValue) {
    return;
  }

  if (newValue === 'up') {
    if (oldValue === 'down') {
      this.rating += 2;
    } else {
      this.rating += 1;
    }
  } else if (newValue === 'down') {
    if (oldValue === 'up') {
      this.rating -= 2;
    } else {
      this.rating -= 1;
    }
  }

  this._vote = newValue;
  this.setAttribute('vote', newValue);
  // add render method
  this.render();
}

Tutaj udało się usunąć logikę aktualizacji DOM z kreatora rating i dodać wywołanie do funkcji render z kodu ustawiającego vote. Szablon jest teraz znacznie bardziej czytelny, ponieważ widać, gdzie stosowane są powiązania i detektory zdarzeń.

Odśwież stronę. Powinien pojawić się działający przycisk oceny, który po naciśnięciu głosu powinien wyglądać tak.

Suwak oceny w górę i w dół z wartością 6 i kciukiem w górę w kolorze zielonym

8. LitElement

Dlaczego LitElement

W kodzie nadal występują pewne problemy. Po pierwsze, jeśli zmienisz właściwość lub atrybut vote, może to spowodować zmianę właściwości rating, co spowoduje dwukrotne wywołanie funkcji render. Chociaż powtarzające się wywołania renderowania są zasadniczo pozbawione eksploatacji i wydajne, maszyna wirtualna JavaScript nadal poświęca czas na wywoływanie tej funkcji 2 razy synchronicznie. Po drugie: dodawanie nowych właściwości i atrybutów jest uciążliwe, ponieważ wymaga kodowania na stałe. Dlatego z pomocą przychodzi LitElement.

LitElement to klasa podstawowa Lit umożliwiająca tworzenie szybkich i lekkich komponentów sieciowych, których można używać na platformach i środowiskach. Następnie zobacz, co LitElement może dla nas zrobić w rating-element, zmieniając implementację tak, aby z niej korzystać.

Korzystanie z LitElement

Zacznij od zaimportowania i podklasyfikacji klasy podstawowej LitElement z pakietu lit:

index.js

import {LitElement, html, css} from 'lit';

class RatingElement extends LitElement {
// remove connectedCallback()
...

Importujesz LitElement, która jest nową klasą podstawową dla rating-element. Dalej zachowujesz import html, a na koniec css pozwala nam definiować literały szablonów z tagami CSS na potrzeby obliczeń matematycznych, szablonów i innych funkcji w standardzie.

Następnie przenieś style z metody renderowania do statycznego arkusza stylów Lit:

index.js

class RatingElement extends LitElement {
  static get styles() {
    return css`
      :host {
        display: inline-flex;
        align-items: center;
      }
      button {
        background: transparent;
        border: none;
        cursor: pointer;
      }

      :host([vote=up]) .thumb_up {
        fill: green;
      }

      :host([vote=down]) .thumb_down {
        fill: red;
      }
    `;
  }
 ...

To tutaj mieszka większość stylów literackich. Lit wykorzysta te style i użyje funkcji przeglądarki, takich jak konstrukcyjne arkusze stylów, aby przyspieszyć renderowanie. W razie potrzeby będzie też przekazywać ten kod przez kod polyfill dotyczący komponentów sieciowych w starszych przeglądarkach.

Cykl życia

Lit wprowadza zestaw metod wywołania zwrotnego cyklu życia renderowania do natywnych wywołań zwrotnych komponentu internetowego. Te wywołania zwrotne są wywoływane po zmianie zadeklarowanych właściwości Lit.

Aby korzystać z tej funkcji, musisz statycznie zadeklarować, które właściwości będą aktywować cykl życia renderowania.

plik index.js

static get properties() {
  return {
    rating: {
      type: Number,
    },
    vote: {
      type: String,
      reflect: true,
    }
  };
}

// remove observedAttributes() and attributeChangedCallback()
// remove set rating() get rating()

Możesz tu zdefiniować, że rating i vote będą uruchamiać cykl życia renderowania LitElement, a także zdefiniować typy, które będą używane do konwertowania atrybutów ciągu na właściwości.

<user-profile .name=${this.user.name} .age=${this.user.age}>
  ${this.user.family.map(member => html`
        <family-member
             .name=${member.name}
             .relation=${member.relation}>
        </family-member>`)}
</user-profile>

Dodatkowo flaga reflect we właściwości vote automatycznie zaktualizuje atrybut vote elementu hosta, który został przez Ciebie ręcznie aktywowany w ustawieniu ustawiający vote.

Teraz, gdy masz już blok właściwości statycznych, możesz usunąć logikę aktualizowania atrybutów i właściwości renderowania. Oznacza to, że możesz usunąć następujące metody:

  • connectedCallback
  • observedAttributes
  • attributeChangedCallback
  • rating (setery i setery)
  • vote (wartości ustalające i pobierające, ale zachowuje logikę zmiany z metody ustawiającej)

Zachowujesz wartość constructor oraz dodajesz nową metodę cyklu życia willUpdate:

index.js

constructor() {
  super();
  this.rating = 0;
  this.vote = null;
}

willUpdate(changedProps) {
  if (changedProps.has('vote')) {
    const newValue = this.vote;
    const oldValue = changedProps.get('vote');

    if (newValue === 'up') {
      if (oldValue === 'down') {
        this.rating += 2;
      } else {
        this.rating += 1;
      }
    } else if (newValue === 'down') {
      if (oldValue === 'up') {
        this.rating -= 2;
      } else {
        this.rating -= 1;
      }
    }
  }
}

// remove set vote() and get vote()

W tym miejscu wystarczy zainicjować logikę rating i vote, a potem przenieść logikę ustawiającą vote do metody cyklu życia willUpdate. Metoda willUpdate jest wywoływana przed render za każdym razem, gdy zmienia się dowolna aktualizacja usługi, ponieważ LitElement grupuje właściwości, które zmieniają się i renderowanie jest asynchroniczne. Zmiany właściwości reaktywnych (takich jak this.rating) w elemencie willUpdate nie będą wywoływać zbędnych wywołań cyklu życia render.

render to metoda cyklu życia LitElement, która wymaga zwrócenia szablonu Lit:

index.js

render() {
  return html`
    <button
        class="thumb_down"
        @click=${() => {this.vote = 'down'}}>
      <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
    </button>
    <span class="rating">${this.rating}</span>
    <button
        class="thumb_up"
        @click=${() => {this.vote = 'up'}}>
      <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
    </button>`;
}

Nie musisz już sprawdzać poziomu cienia głównego ani wywoływać funkcji render zaimportowanej wcześniej z pakietu 'lit'.

Twój element powinien być teraz wyrenderowany w podglądzie; kliknij!

9. Gratulacje

Gratulacje! Udało Ci się utworzyć komponent internetowy od podstaw i przekształcić go w LitElement.

Mała litera (mniej niż 5 KB w formacie gzip), superszybki i przyjemny w kodowaniu. Możesz tworzyć komponenty do wykorzystania przez inne platformy lub z nich tworzyć w pełni aplikacje.

Wiesz już, czym jest komponent sieciowy, jak go utworzyć i jak Lit ułatwia jego budowanie.

Punkt kontroli kodu

Czy chcesz porównać ostateczny kod z naszym? Porównaj je tutaj.

Co dalej?

Zapoznaj się z pozostałymi ćwiczeniami z programowania.

Więcej informacji

Społeczność