Da componente web a elemento luminoso

1. Introduzione

Ultimo aggiornamento: 2021-08-10

Componenti web

I componenti web sono un insieme di API delle piattaforme web che consentono di creare nuovi tag HTML personalizzati, riutilizzabili e incapsulati da utilizzare in pagine web e app web. I componenti e i widget personalizzati creati in base agli standard dei componenti web funzionano su tutti i browser moderni e possono essere utilizzati con qualsiasi libreria o framework JavaScript compatibile con HTML.

Che cosa significa illuminato

Lit è una semplice libreria per creare componenti web veloci e leggeri che funzionano in qualsiasi framework o senza alcun framework. Con Lit puoi creare componenti, applicazioni, sistemi di progettazione condivisibili e altro ancora.

Lit fornisce API per semplificare le attività più comuni dei componenti web, come la gestione delle proprietà, degli attributi e del rendering.

Cosa imparerai a fare

  • Che cos'è un componente web
  • I concetti dei componenti web
  • Come creare un componente web
  • Che cosa sono lit-html e LitElement
  • Che cosa fa Lit su un componente web

Cosa creerai

  • Un componente web Mi piace / Non mi piace vanilla
  • Un componente web basato su LED che mi piace / Non mi piace

Che cosa ti serve

  • Qualsiasi browser moderno aggiornato (Chrome, Safari, Firefox, Chromium Edge). I componenti web funzionano in tutti i browser moderni e i polyfill sono disponibili per Microsoft Internet Explorer 11 e Microsoft Edge non in cromo.
  • Conoscenza di HTML, CSS, JavaScript e Chrome DevTools.

2. Configurazione e alla scoperta di Playground

Accesso al codice

Durante il codelab ci saranno link a Lit Playground come questo:

Il parco giochi è una sandbox del codice che viene eseguita completamente nel browser. Può compilare ed eseguire file TypeScript e JavaScript, nonché risolvere automaticamente le importazioni nei moduli dei nodi. ad es.

// before
import './my-file.js';
import 'lit';

// after
import './my-file.js';
import 'https://unpkg.com/lit?module';

Puoi seguire l'intero tutorial nel parco giochi Lit, utilizzando questi punti di controllo come punto di partenza. Se utilizzi VS Code, puoi utilizzare questi punti di controllo per scaricare il codice iniziale per qualsiasi passaggio e utilizzarli per verificare il tuo lavoro.

Esplorazione dell'UI di un parco giochi illuminato

La barra delle schede del selettore file è etichettata come Sezione 1, la sezione di modifica del codice come Sezione 2, l'anteprima di output come Sezione 3 e il pulsante di ricaricamento anteprima come Sezione 4

Lo screenshot dell'UI di Lit Playground evidenzia le sezioni che utilizzerai in questo codelab.

  1. Selettore file. Nota il pulsante Più...
  2. Editor di file.
  3. Anteprima del codice.
  4. Pulsante Ricarica.
  5. Pulsante di download.

Configurazione VS Code (avanzata)

Ecco i vantaggi dell'utilizzo di questa configurazione VS Code:

  • Controllo del tipo di modello
  • Template Intelligence e completamento automatico

Se hai già installato NPM, VS Code (con il plug-in lit-plugin) e sai come utilizzare tale ambiente, puoi semplicemente scaricare e avviare questi progetti procedendo nel seguente modo:

  • Premi il pulsante di download
  • Estrai il contenuto del file tar in una directory
  • Installa un server di sviluppo in grado di risolvere gli identificatori di moduli bare (il team di Lit consiglia @web/dev-server)
  • Esegui il server di sviluppo e apri il browser (se utilizzi @web/dev-server puoi usare npx web-dev-server --node-resolve --watch --open)
    • Se usi l'esempio package.json, usa npm run serve

3. Definisci un elemento personalizzato

Elementi personalizzati

I componenti web sono una raccolta di quattro API web native. Questi sono:

  • Moduli ES
  • Elementi personalizzati
  • DOM shadow
  • Modelli HTML

Hai già utilizzato la specifica dei moduli ES, che ti consente di creare moduli JavaScript con importazioni ed esportazioni che vengono caricati nella pagina con <script type="module">.

Definizione di un elemento personalizzato

La specifica degli elementi personalizzati consente agli utenti di definire i propri elementi HTML utilizzando JavaScript. I nomi devono contenere un trattino (-) per distinguerli dagli elementi nativi del browser. Cancella il file index.js e definisci una classe di elementi personalizzati:

index.js

class RatingElement extends HTMLElement {}

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

Un elemento personalizzato viene definito associando una classe che estende HTMLElement a un nome tag con trattino. La chiamata a customElements.define indica al browser di associare la classe RatingElement al tagName ‘rating-element'. Ciò significa che ogni elemento del documento con il nome <rating-element> verrà associato a questa classe.

Inserisci un <rating-element> nel corpo del documento e osserva i contenuti visualizzati.

index.html

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

Ora, osservando l'output, puoi vedere che non è stato eseguito alcun rendering. Si tratta di un comportamento previsto, perché non hai indicato al browser la modalità di rendering di <rating-element>. Puoi confermare che la definizione dell'elemento personalizzato è riuscita selezionando il <rating-element> negli strumenti per sviluppatori di Chrome selettore elemento e, nella console, chiamando:

$0.constructor

Che dovrebbe restituire:

class RatingElement extends HTMLElement {}

Ciclo di vita di un elemento personalizzato

Gli elementi personalizzati includono una serie di hook del ciclo di vita. Questi sono:

  • constructor
  • connectedCallback
  • disconnectedCallback
  • attributeChangedCallback
  • adoptedCallback

constructor viene chiamato quando l'elemento viene creato per la prima volta, ad esempio richiamando document.createElement(‘rating-element') o new RatingElement(). Il costruttore è un buon posto per configurare l'elemento, ma in genere è considerata una cattiva pratica eseguire manipolazioni DOM nel costruttore per l'elemento "boot-up" per motivi legati alle prestazioni.

connectedCallback viene chiamato quando l'elemento personalizzato viene collegato al DOM. In genere è qui che avvengono le manipolazioni iniziali del DOM.

disconnectedCallback viene chiamato dopo che l'elemento personalizzato è stato rimosso dal DOM.

attributeChangedCallback(attrName, oldValue, newValue) viene chiamato quando cambia uno qualsiasi degli attributi specificati dall'utente.

adoptedCallback viene chiamato quando l'elemento personalizzato viene adottato da un altro documentFragment nel documento principale tramite adoptNode, ad esempio in HTMLTemplateElement.

DOM di rendering

A questo punto, torna all'elemento personalizzato e associalo a un DOM. Imposta i contenuti dell'elemento quando viene collegato al 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);

In constructor, memorizzi una proprietà dell'istanza denominata rating nell'elemento. Nell'connectedCallback, aggiungi elementi secondari DOM a <rating-element> per visualizzare la valutazione corrente, insieme ai pulsanti Mi piace e Non mi piace.

4. DOM shadow

Perché Shadow DOM?

Nel passaggio precedente, noterai che i selettori nel tag di stile che hai inserito selezionano un elemento di valutazione nella pagina e un pulsante. Ciò potrebbe causare la fuoriuscita di stili dall'elemento e la selezione di altri nodi a cui potresti non voler applicare uno stile. Inoltre, altri stili al di fuori di questo elemento personalizzato potrebbero involontariamente definire i nodi all'interno dell'elemento personalizzato. Ad esempio, prova a inserire un tag di stile nell'intestazione del documento principale:

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>

L'output dovrebbe avere un bordo rosso attorno all'intervallo per la valutazione. Si tratta di un caso banale, ma la mancanza dell'incapsulamento del DOM potrebbe comportare problemi più grandi per applicazioni più complesse. È qui che entra in gioco Shadow DOM.

Collegare una radice ombra

Collega una radice shadow all'elemento ed esegui il rendering del DOM all'interno di tale radice:

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);

Quando aggiorni la pagina, noterai che gli stili nel documento principale non possono più selezionare i nodi all'interno della radice ombra.

Come hai fatto? Nell'elemento connectedCallback hai chiamato this.attachShadow, che associa una radice ombra a un elemento. La modalità open significa che i contenuti shadow sono ispezionabili e rende accessibile anche la radice shadow tramite this.shadowRoot. Dai un'occhiata anche al componente web in Chrome inspector:

L&#39;albero del dom nello strumento di controllo di Chrome. C&#39;è un <rating-element> con a#shadow-root (aperto) come figlio e il DOM precedente all&#39;interno di quel shadowroot.

Ora dovresti vedere una radice shadow espandibile contenente i contenuti. Tutto ciò che si trova all'interno della radice ombra è chiamato DOM ombra. Se selezioni l'elemento di valutazione in Chrome Dev Tools e chiami $0.children, noterai che non restituisce elementi secondari. Questo perché Shadow DOM non è considerato parte dello stesso albero DOM degli elementi figlio diretti, ma piuttosto l'Albero ombra.

DOM chiaro

Un esperimento: aggiungi un nodo come elemento secondario diretto di <rating-element>:

index.html

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

Aggiorna la pagina. Vedrai che questo nuovo nodo DOM nel Light DOM di questo elemento personalizzato non viene visualizzato nella pagina. Questo perché Shadow DOM dispone di funzionalità per controllare il modo in cui i nodi Light DOM vengono proiettati nel dominio delle ombre tramite gli elementi <slot>.

5. Modelli HTML

Perché usare i modelli

L'utilizzo di innerHTML e stringhe letterali modello senza sanificazione potrebbe causare problemi di sicurezza con l'inserimento di script. In passato, i metodi utilizzati includevano l'uso di DocumentFragment, ma includono anche altri problemi come il caricamento delle immagini e l'esecuzione degli script quando vengono definiti i modelli, nonché l'introduzione di ostacoli per la riusabilità. È qui che entra in gioco l'elemento <template>: i modelli forniscono un DOM inerte, un metodo ad alte prestazioni per clonare i nodi, e modelli riutilizzabili.

Utilizzo di Modelli

Quindi, esegui la transizione del componente per utilizzare i modelli 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>

In questo caso, hai spostato i contenuti del DOM in un tag modello nel DOM del documento principale. Ora esegui il refactoring della definizione dell'elemento personalizzato:

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);

Per utilizzare questo elemento del modello, esegui una query sul modello, recuperane il contenuto e cloni i nodi con templateContent.cloneNode in cui l'argomento true esegue un clone diretto. Puoi quindi inizializzare il dom con i dati.

Congratulazioni, ora puoi disporre di un componente web. Purtroppo non è ancora possibile fare nulla, quindi aggiungi qualche funzionalità.

6. Aggiunta di funzionalità

Associazioni di proprietà

Attualmente, l'unico modo per impostare la valutazione sull'elemento rating è creare l'elemento, impostare la proprietà rating sull'oggetto e inserirlo nella pagina. Purtroppo, gli elementi HTML nativi tendono a funzionare in questo modo. Gli elementi HTML nativi tendono ad aggiornarsi con le modifiche alle proprietà e agli attributi.

Fai in modo che l'elemento personalizzato aggiorni la vista quando la proprietà rating cambia aggiungendo le seguenti righe:

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;
}

Aggiungi un setter e un getter per la proprietà di valutazione, quindi aggiorni il testo dell'elemento di valutazione, se è disponibile. Ciò significa che se imposti la proprietà di valutazione sull'elemento, la vista verrà aggiornata; fai un rapido test nella console Dev Tools.

Associazioni di attributi

Ora aggiorna la vista quando l'attributo cambia. Questo valore è simile a un input che aggiorna la sua visualizzazione quando imposti <input value="newValue">. Fortunatamente, il ciclo di vita del componente web include attributeChangedCallback. Aggiorna la valutazione aggiungendo le seguenti righe:

index.js

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

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

Affinché il attributeChangedCallback venga attivato, devi impostare un getter statico per RatingElement.observedAttributes which defines the attributes to be observed for changes. Devi quindi impostare la classificazione in modo dichiarativo nel DOM. Prova:

index.html

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

Ora la valutazione dovrebbe essere aggiornata in modo dichiarativo.

Funzionalità del pulsante

Manca solo la funzionalità del pulsante. Il comportamento di questo componente deve consentire all'utente di esprimere una valutazione positiva o negativa e di fornire un feedback visivo. Puoi implementare questa funzionalità con alcuni listener di eventi e una proprietà riflettente, ma prima aggiorna gli stili per fornire un feedback visivo aggiungendo le seguenti righe:

index.html

<style>
...

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

In DOM shadow, il selettore :host fa riferimento al nodo o all'elemento personalizzato a cui è collegata la radice shadow. In questo caso, se l'attributo vote è "up", il pulsante Mi piace diventerà verde, ma se vote è "down", then it will turn the thumb-down button red. Ora, implementa la logica creando una proprietà o un attributo che riflette vote in modo simile a come hai implementato rating. Inizia con il setter e il getter proprietà:

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;
}

Inizializzi la proprietà dell'istanza _vote con null in constructor e nel setter controlli se il nuovo valore è diverso. In tal caso, regoli la valutazione di conseguenza e, soprattutto, riporti l'attributo vote all'host con this.setAttribute.

A questo punto, configura l'associazione degli attributi:

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;
  }
}

Anche in questo caso, si tratta della stessa procedura utilizzata per l'associazione dell'attributo rating. aggiungi vote a observedAttributes e imposti la proprietà vote in attributeChangedCallback. Infine, aggiungi alcuni listener di eventi di clic per fornire la funzionalità dei pulsanti.

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';
}

Nell'constructor, puoi associare alcuni listener di clic all'elemento e conservare i riferimenti. In connectedCallback ascolti gli eventi di clic sui pulsanti. Nel disconnectedCallback pulisci questi listener e nei listener dei clic, imposti vote in modo appropriato.

Congratulazioni, ora puoi disporre di un componente web completo. prova a fare clic su alcuni pulsanti. Il problema ora è che il mio file JS ora raggiunge 96 righe, il mio file HTML 43 righe e il codice è piuttosto dettagliato e imperativo per un componente così semplice. È qui che entra in gioco il progetto Lit di Google.

7. Lit-html

Checkpoint del codice

Perché lit-html

Innanzitutto, il tag <template> è utile e dalle prestazioni elevate, ma non è pacchettizzato con la logica del componente, quindi è difficile distribuire il modello con il resto della logica. Inoltre, il modo in cui gli elementi del modello vengono utilizzati intrinsecamente si prestano al codice imperativo, il che in molti casi porta a un codice meno leggibile rispetto ai pattern di codifica dichiarativi.

È qui che entra in gioco lit-html. Lit html è il sistema di rendering di Lit che consente di scrivere modelli HTML in JavaScript, per poi eseguirne il rendering ed eseguirne il rendering in modo efficiente insieme ai dati per creare e aggiornare il DOM. È simile alle librerie JSX e VDOM più diffuse, ma viene eseguito in modo nativo nel browser e in molti casi molto più efficiente.

Con l'HTML letterale

Successivamente, esegui la migrazione del componente web nativo rating-element per utilizzare i modelli Lit che utilizzano i valori letterali dei modelli con tag, ovvero funzioni che prendono stringhe del modello come argomenti con una sintassi speciale. Lit sfrutta quindi elementi di base per fornire un rendering rapido e alcune funzionalità di sanificazione per la sicurezza. Inizia eseguendo la migrazione di <template> in index.html in un modello Lit aggiungendo un metodo render() al componente web:

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);
  }
}

Puoi anche eliminare il tuo modello da index.html. In questo metodo di rendering definisci una variabile denominata template e richiami la funzione letterale del modello con tag html. Noterai anche che hai eseguito una semplice associazione di dati all'interno dell'elemento span.rating utilizzando la sintassi di interpolazione letterale del modello di ${...}. Ciò significa che alla fine non avrai più bisogno di aggiornare imperativamente quel nodo. Inoltre, chiami il metodo render acceso, che esegue il rendering sincrono del modello nella radice ombra.

Migrazione alla sintassi dichiarativa

Ora che hai eliminato l'elemento <template>, esegui il refactoring del codice in modo da chiamare il metodo render appena definito. Puoi iniziare sfruttando l'associazione del listener di eventi di lit per cancellare il codice listener:

index.js

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

I modelli Lit possono aggiungere un listener di eventi a un nodo con la sintassi di associazione @EVENT_NAME. In questo caso, aggiorni la proprietà vote ogni volta che l'utente fa clic su questi pulsanti.

Dopodiché, pulisci il codice di inizializzazione del listener di eventi in constructor, connectedCallback e disconnectedCallback:

index.js

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

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

// remove disonnectedCallback and _onUpClick and _onDownClick

Sei stato in grado di rimuovere la logica listener di clic da tutti e tre i callback e persino rimuovere completamente disconnectedCallback. Hai anche potuto rimuovere tutto il codice di inizializzazione DOM da connectedCallback, rendendolo molto più elegante. Ciò significa anche che puoi eliminare i metodi listener _onUpClick e _onDownClick.

Infine, aggiorna i setter delle proprietà in modo che utilizzino il metodo render, in modo che dom possa aggiornarsi quando le proprietà o gli attributi cambiano:

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();
}

In questo caso, hai potuto rimuovere la logica di aggiornamento del dominio dal setter rating e hai aggiunto una chiamata a render dal setter vote. Ora il modello è molto più leggibile, perché ti consente di vedere dove vengono applicati le associazioni e i listener di eventi.

Aggiorna la pagina. Dovresti avere un pulsante di valutazione funzionante che dovrebbe essere simile a questo quando viene premuto il voto positivo.

Dispositivo di scorrimento della valutazione Mi piace e Non mi piace con un valore pari a 6 e il pollice su di colore verde

8. LitElement

Perché LitElement

Esistono ancora alcuni problemi con il codice. Innanzitutto, se modifichi la proprietà o l'attributo vote, questa modifica potrebbe comportare la modifica della proprietà rating, che comporterà la chiamata di render due volte. Nonostante le chiamate ripetute di rendering siano essenzialmente operative ed efficienti, la VM JavaScript dedica ancora tempo a chiamare questa funzione due volte in modo sincrono. In secondo luogo, è noioso aggiungere nuove proprietà e nuovi attributi, in quanto richiede molto codice boilerplate. È qui che entra in gioco LitElement.

LitElement è la classe base di Lit per creare componenti web veloci e leggeri che possono essere utilizzati in diversi framework e ambienti. Ora dai un'occhiata a cosa può fare per noi LitElement in rating-element modificando l'implementazione in modo che lo utilizzi.

Usare LitElement

Inizia importando e creando sottoclassi della classe base LitElement dal pacchetto lit:

index.js

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

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

Importerai LitElement, che è la nuova classe base per rating-element. Dopodiché mantieni l'importazione html e, infine, css, che ci consente di definire valori letterali di modelli con tag CSS per i calcoli matematici, i modelli e altre funzionalità CSS.

Successivamente, sposta gli stili dal metodo di rendering al foglio di stile statico di 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;
      }
    `;
  }
 ...

È qui che la maggior parte degli stili utilizza la lettera Lit. Lit prenderà questi stili e userà funzionalità del browser come Fogli di stile costruibili per fornire tempi di rendering più rapidi e, se necessario, passarlo attraverso il polyfill dei componenti web sui browser meno recenti.

Ciclo di vita

Oltre ai callback nativi del componente web, Lit introduce un insieme di metodi di callback del ciclo di vita del rendering. Questi callback vengono attivati quando le proprietà Lit dichiarate vengono modificate.

Per utilizzare questa funzionalità, devi dichiarare in modo statico le proprietà che attivano il ciclo di vita del rendering.

index.js

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

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

Qui definisci che rating e vote attiveranno il ciclo di vita del rendering di LitElement e definisci i tipi che verranno utilizzati per convertire gli attributi stringa in proprietà.

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

Inoltre, il flag reflect sulla proprietà vote aggiornerà automaticamente l'attributo vote dell'elemento host che hai attivato manualmente nel setter vote.

Ora che hai il blocco delle proprietà statiche, puoi rimuovere tutti gli attributi e la logica di aggiornamento del rendering delle proprietà. Ciò significa che puoi rimuovere i seguenti metodi:

  • connectedCallback
  • observedAttributes
  • attributeChangedCallback
  • rating (setter e getter)
  • vote (setter e getter, ma mantiene la logica di cambiamento del setter)

Conserva constructor e aggiungi un nuovo metodo del ciclo di vita 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()

Qui è sufficiente inizializzare rating e vote e spostare la logica del setter vote sul metodo del ciclo di vita willUpdate. Il metodo willUpdate viene chiamato prima del giorno render ogni volta che viene modificata una proprietà in aggiornamento, perché LitElement raggruppa le modifiche in gruppo delle proprietà e rende il rendering asincrono. Le modifiche alle proprietà reattive (come this.rating) in willUpdate non attiveranno chiamate del ciclo di vita render non necessarie.

Infine, render è un metodo del ciclo di vita di LitElement che richiede di restituire un modello 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>`;
}

Non è più necessario verificare la radice shadow e chiamare la funzione render precedentemente importata dal pacchetto 'lit'.

Ora l'elemento dovrebbe essere visualizzato nell'anteprima. fai un clic!

9. Complimenti

Congratulazioni, hai creato un componente web da zero e lo hai trasformato in LitElement.

Lit è piccolissimo (< 5 kB minimizzati + gzip), velocissimi e molto divertenti da usare per programmare. Puoi creare componenti utilizzabili da altri framework oppure puoi usarlo per creare app a tutti gli effetti.

Ora sai cos'è un componente web, come svilupparne uno e come Lit ne semplifica la creazione.

Checkpoint del codice

Vuoi confrontare il tuo codice finale con il nostro? Confrontalo qui.

Passaggi successivi

Dai un'occhiata ad alcuni degli altri codelab.

Per approfondire

Community