Lit per gli sviluppatori di React

1. Introduzione

Che cos'è Lit

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.

Obiettivi didattici

Come tradurre diversi concetti di React in Lit, ad esempio:

  • JSX e modelli
  • Componenti e accessori
  • Stato e ciclo di vita
  • Hooks
  • Bambini
  • Riferimenti
  • Stato della mediazione

Cosa creerai

Al termine di questo codelab, potrai convertire i concetti dei componenti React nei relativi analoghi Lit.

Che cosa ti serve

  • La versione più recente di Chrome, Safari, Firefox o Edge.
  • Conoscenza di HTML, CSS, JavaScript e Chrome DevTools.
  • Conoscenza di React
  • (Avanzato) Se vuoi un'esperienza di sviluppo ottimale, scarica VS Code. Avrai bisogno anche di lit-plugin per VS Code e NPM.

2. Lit e React

I concetti e le funzionalità principali di Lit sono simili a quelli di React per molti aspetti, ma Lit presenta anche alcune differenze e caratteristiche distintive:

È piccolo

Lit è minuscolo: si riduce a circa 5 kB compressi e con gzip rispetto ai più di 40 kB di React + ReactDOM.

Grafico a barre delle dimensioni del bundle minimizzate e compresse in kB. La barra luminosa è di 5 kb e la reazione + il DOM di reazione è di 42,2 kb

È veloce

Nei benchmark pubblici che confrontano il sistema di creazione di modelli di Lit, lit-html, con il VDOM di React, lit-html risulta 8-10% più veloce di React nel peggiore dei casi e più del 50% più veloce nei casi d'uso più comuni.

LitElement (la classe di base dei componenti di Lit) aggiunge un overhead minimo a lit-html, ma supera le prestazioni di React del 16-30% se si confrontano le funzionalità dei componenti, come l'utilizzo della memoria, l'interazione e i tempi di avvio.

Grafico a barre raggruppate che confronta le prestazioni illuminate con React in millisecondi (il valore più basso è meglio)

Non richiede una build

Con le nuove funzionalità del browser, come i moduli ES e i valori letterali dei modelli con tag, Lit non richiede la compilazione. Ciò significa che gli ambienti di sviluppo possono essere configurati con un tag script + un browser + un server e il gioco è fatto.

Con i moduli ES e le CDN moderne come Skypack o UNPKG, potresti non aver nemmeno bisogno di NPM per iniziare.

Tuttavia, se vuoi, puoi comunque creare e ottimizzare il codice Lit. Il recente consolidamento degli sviluppatori intorno ai moduli ES nativi è stato positivo per Lit: Lit è semplicemente JavaScript normale e non sono necessarie interfacce a riga di comando specifiche per il framework o la gestione delle build.

Indipendente dal framework

I componenti di Lit sono basati su una serie di standard web chiamati componenti web. Ciò significa che la creazione di un componente in Lit funzionerà nei framework attuali e futuri. Se supporta gli elementi HTML, supporta i componenti web.

Gli unici problemi dell'interoperabilità del framework si verificano quando i framework hanno un supporto restrittivo per il DOM. React è uno di questi framework, ma consente di uscire tramite i riferimenti e i riferimenti in React non sono un'esperienza ottimale per gli sviluppatori.

Il team di Lit sta lavorando a un progetto sperimentale chiamato @lit-labs/react che analizzerà automaticamente i componenti Lit e genererà un wrapper React in modo che tu non debba usare i riferimenti.

Inoltre, Custom Elements Everywhere mostra i framework e le librerie che si integrano bene con gli elementi personalizzati.

Supporto TypeScript all'avanguardia

Sebbene sia possibile scrivere tutto il codice Lit in JavaScript, Lit è scritto in TypeScript e il team di Lit consiglia agli sviluppatori di utilizzare anche TypeScript.

Il team di Lit sta collaborando con la community Lit per aiutare a gestire progetti che integrano il controllo del tipo di TypeScript e l’Intellisense nei modelli Lit sia durante lo sviluppo che la creazione con lit-analyzer e lit-plugin.

Screenshot di un IDE che mostra un controllo di tipo improprio per l'impostazione del valore booleano evidenziato su un numero

Screenshot di un IDE che mostra i suggerimenti di IntelliSense

Gli strumenti per sviluppatori sono integrati nel browser

I componenti Lit sono solo elementi HTML nel DOM. Ciò significa che per ispezionare i componenti non devi installare strumenti o estensioni per il browser.

Puoi semplicemente aprire gli strumenti per gli sviluppatori, selezionare un elemento ed esplorarne le proprietà o lo stato.

immagine degli strumenti per sviluppatori di Chrome che mostra che $0 restituisce <mwc-textfield>, $0.value restituisce ciao mondo, $0.outlined restituisce true e {$0} mostra l&#39;espansione della proprietà

È progettato per il rendering lato server (SSR)

Lit 2 è stato creato pensando al supporto di SSR. Al momento della stesura di questo codelab, il team di Lit non ha ancora rilasciato gli strumenti SSR in un formato stabile, ma il team di Lit ha già implementato componenti sottoposti a rendering lato server nei prodotti Google e ha testato SSR nelle applicazioni React. Il team di Lit prevede di rilasciare questi strumenti esternamente su GitHub a breve.

Nel frattempo, puoi seguire l'avanzamento del team di Lit qui.

La partecipazione è bassa

Lit non richiede un impegno significativo per l'utilizzo. Puoi creare componenti in Lit e aggiungerli al tuo progetto esistente. Se non ti piacciono, non devi convertire l'intera app contemporaneamente, dato che i componenti web funzionano in altri framework.

Hai creato un'intera app in Lit e vuoi passare a qualcos'altro? Bene, puoi posizionare la tua applicazione Lit attuale all'interno del nuovo framework ed eseguire la migrazione di ciò che vuoi ai componenti del nuovo framework.

Inoltre, molti framework moderni supportano l'output nei componenti web, il che significa che in genere possono essere inseriti in un elemento Lit.

3. Configurazione ed esplorazione di Playground

Questo codelab può essere eseguito in due modi:

  • Puoi farlo interamente online, nel browser
  • (Avanzato) Puoi farlo sulla tua macchina locale utilizzando VS Code

Accedere al codice

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

Il playground è una sandbox di codice che viene eseguita completamente nel browser. Può compilare ed eseguire file TypeScript e JavaScript e può anche risolvere automaticamente le importazioni nei moduli dei nodi, ad es.

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

// after
import './my-file.js';
import 'https://cdn.skypack.dev/lit';

Puoi svolgere l'intero tutorial nella sandbox Lit, utilizzando questi checkpoint come punti 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 di file è etichettata come Sezione 1, la sezione di modifica del codice come Sezione 2, l&#39;anteprima dell&#39;output come Sezione 3 e il pulsante di ricarica dell&#39;anteprima come Sezione 4

Lo screenshot dell'interfaccia utente 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 di VS Code:

  • Controllo del tipo di modello
  • IntelliSense e compilazione automatica dei modelli

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 i contenuti del file tar in una directory
  • (In caso di risoluzione del problema) configura un tsconfig rapido che generi moduli es ed es2015+
  • Installa un server di sviluppo in grado di risolvere gli specificatori 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 utilizzare npx web-dev-server --node-resolve --watch --open)
    • Se utilizzi l'esempio package.json, utilizza npm run dev

4. JSX e modelli

In questa sezione apprenderai le nozioni di base sui modelli in Lit.

Modelli JSX e Lit

JSX è un'estensione di sintassi di JavaScript che consente agli utenti di React di scrivere facilmente modelli nel codice JavaScript. I modelli di lite hanno uno scopo simile, ovvero esprimere l'interfaccia utente di un componente in funzione del suo stato.

Sintassi di base

In React, il codice JSX di un messaggio di benvenuto sarebbe simile al seguente:

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

Nell'esempio riportato sopra sono presenti due elementi e una variabile "name" inclusa. In Lit, svolgi i seguenti passaggi:

import {html, render} from 'lit';

const name = 'Josh Perez';
const element = html`
  <h1>Hello, ${name}</h1>
  <div>How are you?</div>`;

render(
  element,
  mountNode
);

Tieni presente che i modelli Lit non richiedono un frammento React per raggruppare più elementi nei modelli.

In Lit, i modelli sono racchiusi in un html modello con tag LITeral, da cui deriva il nome di Lit.

Valori del modello

I modelli Lit possono accettare altri modelli Lit, noti come TemplateResult. Ad esempio, aggrega name in tag in corsivo (<i>) e racchiudilo in un valore letterale di modello taggato N.B..Assicurati di utilizzare l'accento grave (`) e non le virgolette singole (').

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

I TemplateResult di Lit possono accettare array, stringhe, altri TemplateResult e direttive.

Come esercizio, prova a convertire il seguente codice React in 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
);

Risposta:

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

Passare e impostare oggetti

Una delle principali differenze tra le sintassi di JSX e di Lit è la sintassi dell'associazione di dati. Ad esempio, prendiamo questo input React con le associazioni:

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

Nell'esempio precedente, viene definito un input che fa quanto segue:

  • Imposta il valore disabilitato su una variabile definita (in questo caso false)
  • Imposta la classe su static-class più una variabile (in questo caso "static-class my-class")
  • Imposta un valore predefinito

In Lit, svolgi i seguenti passaggi:

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

Nell'esempio di Lit, viene aggiunta un'associazione booleana per attivare/disattivare l'attributo disabled.

Successivamente, è presente una associazione diretta all'attributo class anziché a className. È possibile aggiungere più associazioni all'attributo class, a meno che non utilizzi la direttiva classMap, che è un'utilità dichiarativa per attivare/disattivare le classi.

Infine, la proprietà value viene impostata sull'input. A differenza di React, l'elemento di input non verrà impostato come di sola lettura perché segue l'implementazione e il comportamento nativo dell'input.

Sintassi di associazione delle istanze Lit

html`<my-element ?attribute-name=${booleanVar}>`;
  • Il prefisso ? è la sintassi di associazione per attivare/disattivare un attributo in un elemento
  • Equivalente a inputRef.toggleAttribute('attribute-name', booleanVar)
  • Utile per gli elementi che utilizzano disabled perché disabled="false" viene ancora letto come vero dal DOM perché inputElement.hasAttribute('disabled') === true
html`<my-element .property-name=${anyVar}>`;
  • Il prefisso . è la sintassi di associazione per l'impostazione di una proprietà di un elemento
  • Equivalente a inputRef.propertyName = anyVar
  • Ideale per il trasferimento di dati complessi come oggetti, array o classi
html`<my-element attribute-name=${stringVar}>`;
  • Si lega all'attributo di un elemento
  • Equivalente a inputRef.setAttribute('attribute-name', stringVar)
  • Ottimo per valori di base, selettori di regole di stile e querySelector

Gestori del passaggio

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

Nell'esempio riportato sopra, è definito un input che esegue le seguenti operazioni:

  • Registra la parola "click" quando viene fatto clic sull'input
  • Registra il valore dell'input quando l'utente digita un carattere

In Lit, svolgi i seguenti passaggi:

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

Nell'esempio Lit, è stato aggiunto un listener all'evento click con @click.

Poi, invece di utilizzare onChange, è presente un'associazione all'evento input nativo di <input> poiché questo change evento nativo viene attivato solo su blur (reagisci a questi eventi).

Sintassi del gestore di eventi Lit

html`<my-element @event-name=${() => {...}}></my-element>`;
  • Il prefisso @ è la sintassi di binding per un gestore di eventi
  • Equivalente a inputRef.addEventListener('event-name', ...)
  • Utilizza nomi di eventi DOM nativi

5. Componenti e oggetti di scena

In questa sezione scoprirai i componenti e le funzioni della classe Lit. Stato e hook sono trattati in modo più dettagliato nelle sezioni successive.

Componenti della classe e LitElement

L'equivalente di Lit di un componente di classe React è LitElement e il concetto di "proprietà reattive" di Lit è una combinazione di prop e stato di React. Ad esempio:

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

Nell'esempio precedente è presente un componente React che:

  • Visualizza un name
  • Imposta il valore predefinito di name sulla stringa vuota ("")
  • Riassegna name a "Elliott"

Ecco come fare in LitElement

In 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>`
  }
}

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

Nel file HTML:

<!-- index.html -->
<head>
  <script type="module" src="./index.js"></script>
</head>
<body>
  <welcome-banner name="Elliott"></welcome-banner>
</body>

Un riepilogo di ciò che accade nell'esempio riportato sopra:

@property({type: String})
name = '';
  • Definisce una proprietà reattiva pubblica, una parte dell'API pubblica del componente
  • Espone un attributo (per impostazione predefinita) e una proprietà nel componente
  • Definisce come tradurre l'attributo del componente (che sono stringhe) in un valore
static get properties() {
  return {
    name: {type: String}
  }
}
  • Ha la stessa funzione del decorator TS @property, ma viene eseguita in modo nativo in JavaScript
render() {
  return html`<h1>Hello, ${this.name}</h1>`
}
  • Questo viene richiamato ogni volta che viene modificata una proprietà reattiva
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
  ...
}
  • Ciò associa il nome di un tag Elemento HTML a una definizione di classe
  • A causa dello standard Elementi personalizzati, il nome del tag deve includere un trattino (-)
  • this in un LitElement si riferisce all'istanza dell'elemento personalizzato (in questo caso <welcome-banner>)
customElements.define('welcome-banner', WelcomeBanner);
  • Questo è l'equivalente in JavaScript del decoratore TS @customElement
<head>
  <script type="module" src="./index.js"></script>
</head>
  • Importa la definizione dell'elemento personalizzato
<body>
  <welcome-banner name="Elliott"></welcome-banner>
</body>
  • Aggiunge l'elemento personalizzato alla pagina
  • Imposta la proprietà name su 'Elliott'

Componenti delle funzioni

Lit non ha un'interpretazione 1:1 di un componente funzione perché non utilizza JSX o un preprocessore. Tuttavia, è abbastanza semplice comporre una funzione che prende le proprietà ed esegue il rendering del DOM in base a queste proprietà. Ad esempio:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Elliott"/>
ReactDOM.render(
  element,
  mountNode
);

In Lit, sarà:

import {html, render} from 'lit';

function Welcome(props) {
  return html`<h1>Hello, ${props.name}</h1>`;
}

render(
  Welcome({name: 'Elliott'}),
  document.body.querySelector('#root')
);

6. Stato e ciclo di vita

In questa sezione imparerai a conoscere lo stato e il ciclo di vita di Lit.

Stato

Il concetto di "Proprietà reattive" di Lit è un mix di stato e oggetti di React. Se modificate, le proprietà reattive possono attivare il ciclo di vita del componente. Le proprietà reattive sono disponibili in due varianti:

Proprietà reattive pubbliche

// 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';
}
  • Definito da @property
  • Simile a props e state di React, ma mutabile
  • API pubblica accessibile e impostata dai consumer del componente

Stato reattivo interno

// 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';
}
  • Definito da @state
  • Simile allo stato di React, ma modificabile
  • Stato interno privato a cui si accede in genere dall'interno del componente o delle sottoclassi

Lifecycle

Il ciclo di vita di Lit è abbastanza simile a quello di React, ma ci sono alcune differenze significative.

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';
  }
}
  • L'equivalente in litri è anche constructor
  • Non è necessario passare nulla alla videochiamata
  • Richiamato da (non completamente inclusivo):
    • document.createElement
    • document.innerHTML
    • new ComponentClass()
    • Se nella pagina è presente un nome di tag non aggiornato e la definizione viene caricata e registrata con @customElement o customElements.define
  • Funzione simile a constructor della reazione

render

// React
render() {
  return <div>Hello World</div>
}

// Lit
render() {
  return html`<div>Hello World</div>`;
}
  • L'equivalente Lit è anche render
  • Può restituire qualsiasi risultato di cui è possibile eseguire il rendering, ad esempio TemplateResult o string e così via.
  • Come in React, render() deve essere una funzione pura
  • Verrà visualizzato nel nodo restituito da createRenderRoot() (ShadowRoot per impostazione predefinita)

componentDidMount

componentDidMount è simile a una combinazione dei callback del ciclo di vita firstUpdated e connectedCallback di Lit.

firstUpdated

import Chart from 'chart.js';

// React
componentDidMount() {
  this._chart = new Chart(this.chartElRef.current, {...});
}

// Lit
firstUpdated() {
  this._chart = new Chart(this.chartEl, {...});
}
  • Richiamato la prima volta che il modello del componente viene visualizzato nella directory principale del componente
  • Verrà chiamato solo se l'elemento è collegato, ad esempio non viene chiamato tramite document.createElement('my-component') finché il nodo non viene aggiunto all'albero DOM
  • È un ottimo posto per eseguire la configurazione di un componente che richiede il rendering del DOM
  • A differenza di componentDidMount di React, le modifiche alle proprietà reattive in firstUpdated causeranno un nuovo rendering, anche se in genere il browser raggruppa le modifiche nello stesso frame. Se queste modifiche non richiedono l'accesso al DOM della directory principale, in genere dovrebbero essere inserite in willUpdate

connectedCallback

// React
componentDidMount() {
  this.window.addEventListener('resize', this.boundOnResize);
}

// Lit
connectedCallback() {
  super.connectedCallback();
  this.window.addEventListener('resize', this.boundOnResize);
}
  • Richiamato ogni volta che l'elemento personalizzato viene inserito nell'albero DOM
  • A differenza dei componenti React, quando gli elementi personalizzati vengono scollegati dal DOM non vengono eliminati e quindi possono essere "collegati" più volte
    • firstUpdated non verrà più richiamato
  • Utile per reinizializzare il DOM o ricollegare gli ascoltatori di eventi che sono stati ripuliti al momento della disconnessione
  • Nota: connectedCallback potrebbe essere chiamato prima del giorno firstUpdated, quindi alla prima chiamata il DOM potrebbe non essere disponibile

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);
  }
}
  • L'equivalente in lit è updated (utilizzando il tempo passato inglese di "update")
  • A differenza di React, updated viene chiamato anche al rendering iniziale
  • Funzione simile a componentDidUpdate della reazione

componentWillUnmount

// React
componentWillUnmount() {
  this.window.removeEventListener('resize', this.boundOnResize);
}

// Lit
disconnectedCallback() {
  super.disconnectedCallback();
  this.window.removeEventListener('resize', this.boundOnResize);
}
  • L'equivalente in litri è simile a disconnectedCallback
  • A differenza dei componenti React, quando gli elementi personalizzati vengono scollegati dal DOM, il componente non viene distrutto
  • A differenza di componentWillUnmount, disconnectedCallback viene chiamato dopo che l'elemento è stato rimosso dall'albero
  • Il DOM all'interno della radice è ancora collegato al sottoalbero della radice
  • Utile per ripulire gli ascoltatori di eventi e i riferimenti con perdite in modo che il browser possa eseguire la raccolta dei rifiuti del componente

Esercizio

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

Nell'esempio precedente, esiste un orologio semplice che esegue queste operazioni:

  • Viene visualizzato "Hello World! È" e poi mostra l'ora
  • Aggiorna l'orologio ogni secondo
  • Quando viene smontato, viene cancellato l'intervallo che chiama il tick

Inizia con la dichiarazione della classe del componente:

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

Poi, inizializza date e dichiarala una proprietà reattiva interna con @state, poiché gli utenti del componente non imposteranno date direttamente.

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

Poi, esegui il rendering del modello.

// Lit (JS & TS)
render() {
  return html`
    <div>
      <h1>Hello, World!</h1>
      <h2>It is ${this.date.toLocaleTimeString()}.</h2>
    </div>
  `;
}

Ora implementa il metodo tick.

tick() {
  this.date = new Date();
}

Poi viene l'implementazione di componentDidMount. Anche in questo caso, l'analogo Lit è una combinazione di firstUpdated e connectedCallback. Nel caso di questo componente, l'utilizzo di tick con setInterval non richiede l'accesso al DOM all'interno dell'elemento principale. Inoltre, l'intervallo viene cancellato quando l'elemento viene rimosso dall'albero del documento, quindi se viene ricollegato, l'intervallo deve essere riavviato. Pertanto, connectedCallback è una scelta migliore in questo caso.

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

Infine, ripulisci l'intervallo in modo che non esegua il tick dopo che l'elemento è stato scollegato dall'albero del documento.

// Lit (TS & JS)
disconnectedCallback() {
  super.disconnectedCallback();
  clearInterval(this.timerId);
}

Riassumendo, il risultato dovrebbe essere simile a questo:

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

In questa sezione imparerai a tradurre i concetti di hook di React in Lit.

Concetti degli hook di React

Gli hook di reazione consentono di "agganciare" i componenti della funzione allo stato. Questo approccio offre diversi vantaggi.

  • Semplificano il riutilizzo della logica stateful
  • Aiuta a suddividere un componente in funzioni più piccole

Inoltre, l'attenzione ai componenti basati su funzioni ha risolto alcuni problemi relativi alla sintassi basata su classi di React, ad esempio:

  • Passaggio props da constructor a super
  • L'inizializzazione disordinata delle proprietà in constructor
    • Questo era un motivo dichiarato dal team di React all'epoca, ma risolto da ES2019
  • Problemi causati dal fatto che this non fa più riferimento al componente

Concetti di hook di React in Lit

Come menzionato nella sezione Componenti e oggetti, Lit non offre un modo per creare elementi personalizzati da una funzione, ma LitElement risolve la maggior parte dei problemi principali relativi ai componenti delle classi React. Ad esempio:

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

In che modo Lit risolve questi problemi?

  • constructor non accetta argomenti
  • Tutte e @event le associazioni vengono associate automaticamente a this
  • this nella maggior parte dei casi si riferisce al riferimento dell'elemento personalizzato
  • Ora è possibile creare un'istanza per le proprietà delle classi come membri dei corsi. Esegue la pulizia delle implementazioni basate sul costruttore

Controller reattivi

I concetti principali alla base degli hook esistono in Lit come controller reattivi. I pattern di controller reattivi consentono la condivisione della logica stateful, la suddivisione dei componenti in bit più piccoli e più modulari e il collegamento al ciclo di vita di aggiornamento di un elemento.

Un controller reattivo è un'interfaccia oggetto che può essere collegata al ciclo di vita dell'aggiornamento di un host controller come LitElement.

Il ciclo di vita di un ReactiveController e di un reactiveControllerHost è il seguente:

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

Se crei un controller reattivo e lo colleghi a un host con addController, il ciclo di vita del controller verrà chiamato insieme a quello dell'host. Ad esempio, ricorda l'esempio dell'orologio nella sezione Stato e ciclo di vita:

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

Nell'esempio precedente, un orologio semplice esegue le seguenti operazioni:

  • Viene visualizzato "Hello World! È" e poi mostra l'ora
  • Aggiorna l'orologio ogni secondo
  • Quando il dispositivo è smontato viene cancellato l'intervallo che chiama il segno di spunta.

Creazione dell'impalcatura del componente

Inizia con la dichiarazione della classe del componente e aggiungi la funzione 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);

Creazione del controller

Ora passa a clock.ts, crea un corso per ClockController e configura 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() {
  }
}

Un controller reattivo può essere creato in qualsiasi modo purché condivida l'interfaccia ReactiveController, ma l'utilizzo di una classe con un constructor che può accettare un'interfaccia ReactiveControllerHost e qualsiasi altra proprietà necessaria per inizializzare il controller è un pattern che il team di Lit preferisce utilizzare per la maggior parte dei casi di base.

Ora devi tradurre i callback del ciclo di vita React in callback del controller. In breve:

  • componentDidMount
    • A connectedCallback di LitElement
    • Al dispositivo hostConnected del controller
  • ComponentWillUnmount
    • A disconnectedCallback di LitElement
    • Al hostDisconnected del controller

Per saperne di più sulla traduzione del ciclo di vita della reazione al ciclo di vita Lit, consulta la sezione Stato e ciclo di vita.

Successivamente, implementa il callback hostConnected e i metodi tick e pulisci l'intervallo in hostDisconnected come mostrato nell'esempio nella sezione Stato e ciclo di vita.

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

Utilizzare il controller

Per utilizzare il controller dell'orologio, importa il controller e aggiorna il componente in index.ts o 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);

Per utilizzare il controller, devi creare un'istanza del controller passando un riferimento all'host del controller (che è il componente <my-element>), quindi utilizzare il controller nel metodo render.

Attivazione di nuovi rendering nel controller

Tieni presente che verrà visualizzata l'ora, ma non viene aggiornata. Questo accade perché il controller imposta la data ogni secondo, ma l'host non si aggiorna. Questo perché date sta cambiando nella classe ClockController e non più nel componente. Ciò significa che dopo aver impostato date sul controller, all'host deve essere chiesto di eseguire il ciclo di vita dell'aggiornamento con host.requestUpdate().

// Lit (TS & JS) - clock.ts / clock.js
private tick() {
  this.date = new Date();
  this.host.requestUpdate();
}

Ora il tempo dovrebbe far suonare!

Per un confronto più approfondito dei casi d'uso comuni con gli hook, consulta la sezione Argomenti avanzati - Hook.

8. Bambini

In questa sezione imparerai a utilizzare gli slot per gestire i figli in Lit.

Slot machine e bambini

Gli slot consentono la composizione consentendo di nidificare i componenti.

In React, i bambini vengono ereditati tramite gli oggetti di scena. L'area predefinita è props.children e la funzione render definisce la posizione dell'area predefinita. Ad esempio:

const MyArticle = (props) => {
 return <article>{props.children}</article>;
};

Tieni presente che props.children sono componenti di reazione e non elementi HTML.

In Lit, i bambini sono composti nella funzione di rendering con elementi slot. Tieni presente che gli elementi secondari non vengono ereditati nello stesso modo di React. In Lit, gli elementi secondari sono HTMLElements associati agli slot. Questo allegato è chiamato Proiezione.

@customElement("my-article")
export class MyArticle extends LitElement {
  render() {
    return html`
      <article>
        <slot></slot>
      </article>
   `;
  }
}

Più slot

In React, l'aggiunta di più slot è essenzialmente equivalente all'eredità di più prop.

const MyArticle = (props) => {
  return (
    <article>
      <header>
        {props.headerChildren}
      </header>
      <section>
        {props.sectionChildren}
      </section>
    </article>
  );
};

Allo stesso modo, l'aggiunta di più elementi <slot> crea più spazi in Lit. Sono definiti più slot con l'attributo name: <slot name="slot-name">. In questo modo, i bambini possono dichiarare a quale fascia oraria verrà assegnato il loro turno.

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

Contenuti slot predefiniti

Gli slot mostreranno il sottoalbero quando non ci sono nodi previsti per quell'area. Quando vengono proiettati i nodi in uno slot, quest'ultimo non mostrerà il sottoalbero ma i nodi proiettati.

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

Assegnare i bambini agli spazi

In React, gli elementi secondari vengono assegnati alle aree tramite le proprietà di un componente. Nell'esempio seguente, gli elementi React vengono passati ai prop headerChildren e sectionChildren.

const MyNewsArticle = () => {
 return (
   <MyArticle
     headerChildren={<h3>Extry, Extry! Read all about it!</h3>}
     sectionChildren={<p>Children are props in React!</p>}
   />
 );
};

In Lit, i bambini vengono assegnati agli slot utilizzando l'attributo 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>
   `;
  }
}

Se non è presente uno slot predefinito (ad es. <slot>) e non è presente uno slot con un attributo name (ad es. <slot name="foo">) che corrisponda all'attributo slot degli elementi secondari dell'elemento personalizzato (ad es. <div slot="foo">), il nodo non verrà proiettato e non verrà visualizzato.

9. Riferimenti

A volte, uno sviluppatore potrebbe dover accedere all'API di un HTMLElement.

In questa sezione scoprirai come acquisire i riferimenti agli elementi in Lit.

Riferimenti a React

Un componente React viene trasferito in una serie di chiamate di funzione che creano un DOM virtuale quando viene richiamato. Questo DOM virtuale viene interpretato da ReactDOM e visualizza gli elementi HTMLElement.

In React, i Refs sono uno spazio in memoria per contenere un HTMLElement generato.

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

Nell'esempio precedente, il componente React:

  • Esegui il rendering di un'input di testo vuoto e di un pulsante con testo
  • Mettere in primo piano l'input quando si fa clic sul pulsante

Dopo il rendering iniziale, React imposterà inputRef.current sul valore HTMLInputElement generato tramite l'attributo ref.

"Riferimenti" illuminato con @query

Lit è integrato nel browser e crea un'astrazione molto sottile sulle funzionalità native del browser.

La reazione equivalente a refs in Lit è l'elemento HTMLElement restituito dai decoratori @query e @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>
   `;
  }
}

Nell'esempio precedente, il componente Lit esegue le seguenti operazioni:

  • Definisce una proprietà su MyElement utilizzando il decorator @query (creazione di un getter per un HTMLInputElement).
  • Dichiara e allega un callback dell'evento di clic denominato onButtonClick.
  • Imposta lo stato attivo sull'input al clic del pulsante

In JavaScript, i decorator @query e @queryAll eseguono rispettivamente querySelector e querySelectorAll. È l'equivalente JavaScript di @query('input') inputEl!: HTMLInputElement;

get inputEl() {
  return this.renderRoot.querySelector('input');
}

Dopo che il componente Lit esegue il commit del modello del metodo render nella radice di my-element, il decorator @query consentirà a inputEl di restituire il primo elemento input trovato nella radice di rendering. Restituisce null se @query non riesce a trovare l'elemento specificato.

Se ci fossero più elementi input nella radice di rendering, @queryAll restituisce un elenco di nodi.

10. Stato della mediazione

In questa sezione imparerai come mediare lo stato tra i componenti in Lit.

Componenti riutilizzabili

React imita le pipeline di rendering funzionali con flusso di dati dall'alto verso il basso. I componenti principali forniscono lo stato ai componenti secondari tramite gli elementi di scena. I bambini comunicano con i genitori tramite i callback trovati negli elementi di scena.

const CounterButton = (props) => {
  const label = props.step < 0
    ? `- ${-1 * props.step}`
    : `+ ${props.step}`;


  return (
    <button
      onClick={() =>
        props.addToCounter(props.step)}>{label}</button>
  );
};

Nell'esempio riportato sopra, un componente React esegue le seguenti operazioni:

  • Crea un'etichetta basata sul valore props.step.
  • Visualizza un pulsante con l'etichetta +step o -step
  • Aggiorna il componente principale chiamando props.addToCounter con props.step come argomento al clic

Sebbene sia possibile passare i callback in Lit, i pattern convenzionali sono diversi. Il componente React nell'esempio precedente potrebbe essere scritto come componente illuminato nell'esempio seguente:

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

Nell'esempio precedente, un componente illuminato esegue le seguenti operazioni:

  • Crea la proprietà reattiva step
  • Invia un evento personalizzato denominato update-counter che porta il valore step dell'elemento al clic

Gli eventi del browser vengono visualizzati dagli elementi secondari agli elementi principali. Gli eventi consentono ai bambini di trasmettere eventi di interazione e modifiche dello stato. Reagisce fondamentalmente passa lo stato nella direzione opposta, quindi è raro vedere i componenti React inviare e ascoltare gli eventi nello stesso modo dei componenti illuminati.

Componenti stateful

In React, è comune utilizzare gli hook per gestire lo stato. Un componente MyCounter può essere creato riutilizzando il componente CounterButton. Nota come addToCounter viene passato a entrambe le istanze di 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>
 );
};

Nell'esempio precedente:

  • Crea uno stato count.
  • Crea un callback che aggiunge un numero a uno stato count.
  • CounterButton utilizza addToCounter per aggiornare count di step a ogni clic.

È possibile ottenere un'implementazione simile di MyCounter in Lit. Tieni presente che addToCounter non viene passato a counter-button. Il callback è invece associato come listener di eventi all'evento @update-counter in un elemento principale.

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

L'esempio riportato sopra esegue le seguenti operazioni:

  • Crea una proprietà reattiva denominata count che aggiorna il componente quando il valore viene modificato
  • Collega il callback addToCounter al listener di eventi @update-counter
  • Aggiorna count aggiungendo il valore trovato nella sezione detail.step dell'evento update-counter
  • Imposta il valore step di counter-button tramite l'attributo step

È più convenzionale utilizzare proprietà reattive in Lit per trasmettere i cambiamenti da genitori a figli. Analogamente, è buona prassi utilizzare il sistema di eventi del browser per visualizzare i dettagli dal basso verso l'alto.

Questo approccio segue le best practice e rispetta lo scopo di Lit di fornire supporto multipiattaforma per i componenti web.

11. Stili

In questa sezione scoprirai come applicare uno stile in

Stili

Lit offre diversi modi per applicare uno stile agli elementi, oltre a una soluzione integrata.

Stili in linea

Lit supporta gli stili in linea e la loro associazione.

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

Nell'esempio riportato sopra sono presenti due intestazioni, ciascuna con uno stile in linea.

Ora importa e vincola un bordo da border-color.js al testo arancione:

...
import borderColor from './border-color.js';

...

html`
  ...
  <h1 style="color:orange;${borderColor}">This text is orange</h1>
  ...`

Dover calcolare la stringa di stile ogni volta può essere un po' fastidioso, quindi Lit offre un'istruzione per risolvere questo problema.

styleMap

La direttiva styleMap semplifica l'utilizzo di JavaScript per impostare gli stili in linea. Ad esempio:

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

L'esempio riportato sopra esegue le seguenti operazioni:

  • Viene visualizzato un h1 con un bordo e un selettore di colori
  • Modifica border-color in base al valore del selettore di colori

Inoltre, è presente styleMap, che viene utilizzato per impostare gli stili di h1. styleMap segue una sintassi simile alla sintassi di associazione degli attributi style di React.

CSSResult

Il modo consigliato per applicare uno stile ai componenti è utilizzare il valore letterale del modello con tag 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>
    `;
  }
}

L'esempio sopra fa quanto segue:

  • Dichiara un valore letterale di modello con tag CSS con un'associazione
  • Imposta i colori di due h1 con ID

Vantaggi dell'utilizzo del tag modello css:

  • Analizzati una volta per classe o per istanza
  • Implementata tenendo presente la possibilità di riutilizzare i moduli
  • Può separare facilmente gli stili all'interno dei propri file
  • Compatibile con il polyfill delle proprietà personalizzate CSS

Inoltre, prendi nota del tag <style> in index.html:

<!-- index.html -->
<style>
  h1 {
    color: red !important;
  }
</style>

Lit estende l'ambito degli stili dei componenti alle relative origini. Ciò significa che gli stili non si diffonderanno e non cadranno. Per trasferire gli stili ai componenti, il team di Lit consiglia di utilizzare le proprietà personalizzate CSS in quanto possono penetrare l'ambito degli stili Lit.

Tag di stile

È anche possibile semplicemente incorporare i tag <style> nei modelli. Il browser deduplica questi tag di stile, ma, posizionandoli nei modelli, verranno analizzati per istanza del componente e non per classe, come nel caso del modello con tag css. Inoltre, la deduplicazione del browser di CSSResult è molto più veloce.

Anche l'utilizzo di un elemento <link rel="stylesheet"> nel modello è una possibilità per gli stili, ma è sconsigliato poiché potrebbe causare un flash iniziale di contenuti senza stile (FOUC).

12. Argomenti avanzati (facoltativo)

JSX e modelli

DOM di Lit e virtuale

Lit-html non include un DOM virtuale convenzionale che differenzia ogni singolo nodo. Utilizza invece le funzionalità di prestazioni intrinseche alla specifica del letterale di modello con tag di ES2015. I letterali di modello con tag sono stringhe di letterali di modello con funzioni di tag associate.

Ecco un esempio di valore letterale di modello:

const str = 'string';
console.log(`This is a template literal ${str}`);

Ecco un esempio di un valore letterale di modello con tag:

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

Nell'esempio precedente, il tag è la funzione tag e la funzione f restituisce una chiamata a un valore letterale di modello taggato.

Molte delle prestazioni straordinarie in Lit derivano dal fatto che gli array di stringhe passati nella funzione tag hanno lo stesso puntatore (come mostrato nel secondo console.log). Il browser non ricrea un nuovo array strings per ogni chiamata di funzione tag, perché utilizza lo stesso valore letterale modello (ovvero nella stessa posizione nell'AST). Quindi l'associazione, l'analisi e la memorizzazione nella cache dei modelli di Lit possono sfruttare queste caratteristiche senza un overhead per le differenze di runtime.

Questo comportamento del browser integrato dei literali di modello con tag offre a Lit un notevole vantaggio in termini di prestazioni. La maggior parte dei DOM virtuali convenzionali svolge la maggior parte del lavoro in JavaScript. Tuttavia, i valori letterali dei modelli con tag eseguono la maggior parte delle differenze nel C++ del browser.

Se vuoi iniziare a utilizzare le literali di template con tag HTML con React o Preact, il team di Lit consiglia la libreria htm.

Tuttavia, come nel caso del sito Google Codelabs e di diversi editor di codice online, noterai che l'evidenziazione della sintassi dei literali di modello con tag non è molto comune. Alcuni IDE e editor di testo li supportano per impostazione predefinita, ad esempio Atom e lo strumento di evidenziazione del codice di GitHub. Inoltre, il team di Lit lavora a stretto contatto con la community per gestire progetti come lit-plugin, un plug-in VS Code che aggiunge l'evidenziazione della sintassi, il controllo del tipo e l'intelligenza dei progetti Lit.

Lit e JSX + React DOM

JSX non viene eseguito nel browser e utilizza invece un preprocessore per convertire JSX in chiamate di funzione JavaScript (in genere tramite Babel).

Ad esempio, Babel trasformerà questo:

const element = <div className="title">Hello World!</div>;
ReactDOM.render(element, mountNode);

in questo modo:

const element = React.createElement('div', {className: 'title'}, 'Hello World!');
ReactDOM.render(element, mountNode);

React DOM prende quindi l'output React e lo converte nel DOM effettivo: proprietà, attributi, listener di eventi e tutto il resto.

Lit-html utilizza valori letterali di modelli con tag che possono essere eseguiti nel browser senza traspirazione o preprocessore. Ciò significa che per iniziare a utilizzare Lit, hai bisogno solo di un file HTML, uno script del modulo ES e un server. Ecco uno script completamente eseguibile dal browser:

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

Inoltre, poiché il sistema di modelli di Lit, lit-html, non utilizza un DOM virtuale convenzionale ma utilizza direttamente l'API DOM, la dimensione di Lit 2 è inferiore a 5kb minimizzati e compressi rispetto a React (2,8kb) + 40kb minimizzati e compressi di reazione-dom (39,4kb) .

Eventi

React utilizza un sistema di eventi sintetici. Ciò significa che react-dom deve definire tutti gli eventi che verranno utilizzati in ogni componente e fornire un ascoltatore di eventi camelCase equivalente per ogni tipo di nodo. Di conseguenza, JSX non dispone di un metodo per definire un listener di eventi per un evento personalizzato e gli sviluppatori devono utilizzare un ref e quindi applicare in modo imperativo un listener. Ciò crea un'esperienza utente non soddisfacente quando si integrano librerie che non sono progettate per React, con la conseguenza di dover scrivere un wrapper specifico per React.

Lit-html accede direttamente al DOM e utilizza eventi nativi, quindi aggiungere ascoltatori di eventi è semplicissimo. Ciò significa che viene effettuata una minore analisi del runtime per l'aggiunta di listener di eventi e di eventi di attivazione.

Componenti e accessori

Componenti React ed elementi personalizzati

Dietro le quinte, LitElement utilizza elementi personalizzati per pacchettizzare i suoi componenti. Gli elementi personalizzati introducono alcuni compromessi tra i componenti React in termini di componenti (stato e ciclo di vita sono illustrati più avanti nella sezione Stato e ciclo di vita).

Ecco alcuni vantaggi di Custom Elements come sistema di componenti:

  • Nativi del browser e non richiedono strumenti
  • Adattarsi a ogni API browser da innerHTML e document.createElement a querySelector
  • In genere possono essere utilizzati in più framework
  • Può essere registrato in modo lazy con customElements.define e "hydrate" DOM

Alcuni svantaggi di Custom Elements rispetto ai componenti React:

  • Non è possibile creare un elemento personalizzato senza definire una classe (quindi non sono ammessi componenti funzionali come JSX)
  • Deve contenere un tag di chiusura
    • Nota: nonostante la praticità per gli sviluppatori, i fornitori di browser tendono a non gradire la specifica dei tag autochiudenti, motivo per cui le specifiche più recenti tendono a non includerli.
  • Introduce un nodo aggiuntivo nell'albero DOM che potrebbe causare problemi di layout
  • Deve essere registrato tramite JavaScript

Lit ha adottato elementi personalizzati invece di un sistema di elementi su misura perché questi ultimi sono integrati nel browser. Il team di Lit ritiene che i vantaggi del cross-framework superino quelli offerti da un livello di astrazione dei componenti. Infatti, gli sforzi del team di Lit nello spazio lit-ssr hanno superato i problemi principali con la registrazione JavaScript. Inoltre, alcune aziende come GitHub sfruttano la registrazione lazy degli elementi personalizzati per migliorare progressivamente le pagine con elementi facoltativi.

Trasmissione di dati agli elementi personalizzati

Un equivoco comune con gli elementi personalizzati è che i dati possono essere trasmessi solo come stringhe. Questo malinteso probabilmente deriva dal fatto che gli attributi degli elementi possono essere scritti solo come stringhe. Sebbene sia vero che Lit eseguirà il casting degli attributi di stringa ai relativi tipi definiti, gli elementi personalizzati possono anche accettare dati complessi come proprietà.

Ad esempio, data la seguente definizione di 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>`;
  }
}

Viene definita una proprietà reattiva primitiva num che converte il valore di stringa di un attributo in un number, quindi viene introdotta una struttura di dati complessa con attribute:false che disattiva la gestione degli attributi di Lit.

Ecco come trasmettere dati a questo elemento personalizzato:

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

Stato e ciclo di vita

Altri callback del ciclo di vita di React

static getDerivedStateFromProps

Non esiste un equivalente in Lit perché props e state sono entrambe proprietà di classe

shouldComponentUpdate

  • L'equivalente in litri è shouldUpdate
  • Viene chiamato al primo rendering, a differenza di React
  • Funzione simile a shouldComponentUpdate di React

getSnapshotBeforeUpdate

In Lit, getSnapshotBeforeUpdate è simile sia a update che a willUpdate

willUpdate

  • Chiamata effettuata prima del giorno update
  • A differenza di getSnapshotBeforeUpdate, willUpdate viene chiamato prima di render
  • Le modifiche alle proprietà reattive in willUpdate non riattivano il ciclo di aggiornamento
  • Un buon punto per calcolare i valori delle proprietà che dipendono da altre proprietà e vengono utilizzati nel resto della procedura di aggiornamento
  • Questo metodo viene chiamato sul server in SSR, quindi non è consigliabile accedere al DOM in questo caso

update

  • Chiamata dopo il giorno willUpdate
  • A differenza di getSnapshotBeforeUpdate, update viene chiamato prima di render
  • Le modifiche alle proprietà reattive in update non riattivano il ciclo di aggiornamento se vengono modificate prima di chiamare super.update
  • Ottimo punto per acquisire informazioni dal DOM che circonda il componente prima che l'output visualizzato venga eseguito nel DOM
  • Questo metodo non viene chiamato sul server in SSR

Altri callback del ciclo di vita di Lit

Esistono diversi callback del ciclo di vita che non sono stati menzionati nella sezione precedente perché non hanno un analogo in React. Sono:

attributeChangedCallback

Viene richiamato quando uno degli elementi observedAttributes dell'elemento cambia. Sia observedAttributes che attributeChangedCallback fanno parte della specifica degli elementi personalizzati e sono implementati da Lit under the hood per fornire un'API degli attributi per gli elementi Lit.

adoptedCallback

Richiamato quando il componente viene spostato in un nuovo documento, ad esempio da documentFragment di HTMLTemplateElement all'elemento document principale. Questo callback fa parte anche delle specifiche degli elementi personalizzati e deve essere utilizzato solo per casi d'uso avanzati quando il componente modifica i documenti.

Altri metodi e proprietà del ciclo di vita

Questi metodi e proprietà sono membri della classe che puoi chiamare, eseguire l'override o attendere per aiutare a manipolare il processo del ciclo di vita.

updateComplete

Si tratta di un Promise che si risolve al termine dell'aggiornamento dell'elemento perché i cicli di vita di aggiornamento e rendering sono asincroni. Un esempio:

async nextButtonClicked() {
  this.step++;
  // Wait for the next "step" state to render
  await this.updateComplete;
  this.dispatchEvent(new Event('step-rendered'));
}

getUpdateComplete

Questo è un metodo che deve essere sostituito per personalizzare il momento in cui updateComplete viene risolto. Questo accade spesso quando un componente esegue il rendering di un componente secondario e i relativi cicli di rendering devono essere sincronizzati. Ad esempio:

class MyElement extends LitElement {
  ...
  async getUpdateComplete() {
    await super.getUpdateComplete();
    await this.myChild.updateComplete;
  }
}

performUpdate

Questo metodo chiama i callback del ciclo di vita dell'aggiornamento. In genere non dovrebbe essere necessario, tranne in rari casi in cui l'aggiornamento deve essere eseguito in modo sincrono o per la pianificazione personalizzata.

hasUpdated

Questa proprietà è true se il componente è stato aggiornato almeno una volta.

isConnected

Componente della specifica degli elementi personalizzati, questa proprietà sarà true se l'elemento è attualmente collegato alla struttura ad albero del documento principale.

Visualizzazione del ciclo di vita dell'aggiornamento di Lit

Il ciclo di vita dell'aggiornamento è costituito da 3 parti:

  • Prima dell'aggiornamento
  • Aggiorna
  • Post-aggiornamento

Pre-aggiornamento

Un grafo diretto aciclico di nodi con nomi di callback. Il costruttore per requestUpdate. Da @property a Proprietà Setter. attributeChangedCallback a Proprietà. L&#39;impostazione della proprietà in hasChanged. hasChanged è requestUpdate. requestUpdate rimanda al grafico del ciclo di vita dell&#39;aggiornamento successivo.

Dopo il giorno requestUpdate, è previsto un aggiornamento pianificato.

Aggiorna

Un grafo diretto aciclico di nodi con nomi di callback. La freccia indica l&#39;immagine precedente dei punti del ciclo di vita precedenti all&#39;aggiornamento per performUpdate. performUpdate a wasUpdate. whereUpdate puntano sia su &quot;complete update if false&quot; che su willUpdate. willUpdate per aggiornare. Esegui l&#39;aggiornamento sia per il rendering che per il grafico del ciclo di vita successivo all&#39;aggiornamento. Anche il rendering rimanda al grafico del ciclo di vita successivo all&#39;aggiornamento.

Dopo l'aggiornamento

Un grafo diretto aciclico di nodi con nomi di callback. La freccia dall&#39;immagine precedente del ciclo di vita dell&#39;aggiornamento indica firstUpdated. firstUpdated a updated. updated a updateComplete.

Hooks

Perché hook

Gli hook sono stati introdotti in React per casi d'uso di componenti di funzione semplici che richiedevano uno stato. In molti casi semplici, i componenti funzione con hook tendono ad essere molto più semplici e leggibili rispetto alle controparti dei componenti di classe. Tuttavia, quando vengono introdotti aggiornamenti dello stato asincroni e il passaggio di dati tra hook o effetti, il pattern hook tende a non essere sufficiente e una soluzione basata su classi come i controller reattivi tende a essere la scelta migliore.

Hook e controller di richieste API

È comune scrivere un hook che richiede dati da un'API. Ad esempio, prendi in considerazione questo componente della funzione React che esegue quanto segue:

  • index.tsx
    • Visualizza il testo
    • Viene visualizzata la risposta di useAPI
      • ID utente + nome utente
      • Messaggio di errore
        • 404 quando raggiunge l'utente 11 (come previsto)
        • Errore di interruzione se il recupero dell'API viene interrotto
      • Caricamento del messaggio
    • Mostra un pulsante di azione
      • Utente successivo: che recupera l'API per l'utente successivo
      • Annulla: interrompe il recupero dell'API e mostra un errore
  • useApi.tsx
    • Definisce un hook personalizzato useApi
    • Recupera in modo asincrono un oggetto utente da un'API
    • Emissioni:
      • Nome utente
      • Indica se il recupero è in corso
      • Eventuali messaggi di errore
      • Un callback per interrompere il recupero
    • Interrompi i recuperi in corso se smontato

Qui puoi trovare l'implementazione di Lit + Reactive Controller.

Concetti principali:

  • I controller reattivi sono molto simili agli hook personalizzati
  • Passaggio di dati non visualizzabili tra callback ed effetti
    • La reazione utilizza useRef per trasmettere dati tra il giorno useEffect e il giorno useCallback
    • Lit utilizza una proprietà di una classe privata
    • La reazione è essenzialmente una simulazione del comportamento di una proprietà di una classe privata

Inoltre, se ti piace molto la sintassi del componente funzione React con gli hook, ma vuoi lo stesso ambiente senza compilazione di Lit, il team di Lit consiglia vivamente la libreria Haunted.

Bambini

Slot predefinito

Quando agli elementi HTML non viene assegnato un attributo slot, vengono assegnati allo slot senza nome predefinito. Nell'esempio seguente, MyApp inserirà un paragrafo in un'area denominata. L'altro paragrafo verrà inserito per impostazione predefinita nello spazio senza nome".

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

Aggiornamenti slot

Quando la struttura dei discendenti delle aree annuncio cambia, viene attivato un evento slotchange. Un componente Lit può associare un gestore eventi a un evento slotchange. Nell'esempio seguente, i assignedNodes del primo slot trovato in shadowRoot verranno registrati nella console il giorno 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>
   `;
  }
}

Riferimenti

Generazione di riferimenti

Sia Lit che React espongono un riferimento a un HTMLElement dopo l'esecuzione delle relative funzioni render. Tuttavia, vale la pena esaminare come React e Lit compongono il DOM che viene successivamente restituito tramite un decoratore @query di Lit o un riferimento React.

React è una pipeline funzionale che crea componenti React, non elementi HTMLElement. Poiché un Ref viene dichiarato prima che venga visualizzato un HTMLElement, viene allocato uno spazio in memoria. Questo è il motivo per cui viene visualizzato null come valore iniziale di un riferimento, perché l'elemento DOM effettivo non è ancora stato creato (o visualizzato), ovvero useRef(null).

Dopo che ReactDOM ha convertito un componente React in un HTMLElement, cerca un attributo denominato ref in ReactComponent. Se disponibile, ReactDOM inserisce il riferimento dell'elemento HTMLElement a ref.current.

LitElement utilizza la htmlfunzione del tag template di lit-html per comporre un elemento modello sotto il cofano. LitElement stampa i contenuti del modello nello shadow DOM di un elemento personalizzato dopo il rendering. Lo shadow DOM è un albero DOM con ambito incapsulato da un elemento radice shadow. Il decorator @query crea quindi un getter per la proprietà, che in sostanza esegue un this.shadowRoot.querySelector sulla radice con ambito.

Query su più elementi

Nell'esempio riportato di seguito, il decorator @queryAll restituirà i due paragrafi nella radice ombra come 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>
   `;
  }
}

Essenzialmente, @queryAll crea un getter per paragraphs che restituisce i risultati di this.shadowRoot.querySelectorAll(). In JavaScript, è possibile dichiarare che un getter ha lo stesso scopo:

get paragraphs() {
  return this.renderRoot.querySelectorAll('p');
}

Elementi di modifica delle query

Il decoratore @queryAsync è più adatto per gestire un nodo che può cambiare in base allo stato di un'altra proprietà dell'elemento.

Nell'esempio seguente, @queryAsync troverà il primo elemento paragrafo. Tuttavia, un elemento paragrafo viene visualizzato solo quando renderParagraph genera in modo casuale un numero dispari. L'istruzione @queryAsync restituirà una promessa che verrà risolta quando sarà disponibile il primo paragrafo.

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

Stato della mediazione

In React, la convenzione prevede l'uso dei callback perché lo stato è mediato dalla reazione stessa. Conviene non fare affidamento sullo stato fornito dagli elementi. Il DOM è semplicemente un effetto del processo di rendering.

Stato esterno

È possibile utilizzare Redux, MobX o qualsiasi altra libreria di gestione dello stato insieme a Lit.

I componenti Lit vengono creati nell'ambito del browser. Pertanto, qualsiasi libreria presente anche nell'ambito del browser è disponibile per Lit. Sono state costruite molte librerie straordinarie per utilizzare i sistemi di gestione degli stati esistenti in Lit.

Ecco una serie di Vaadin che spiega come utilizzare Redux in un componente Lit.

Dai un'occhiata a lit-mobx di Adobe per vedere come un sito su larga scala può sfruttare MobX in Lit.

Consulta anche Apollo Elements per vedere in che modo gli sviluppatori includono GraphQL nei loro componenti web.

Lit è compatibile con le funzionalità native del browser e la maggior parte delle soluzioni di gestione dello stato nell'ambito del browser può essere utilizzata in un componente Lit.

Stili

DOM shadow

Per incapsulare in modo nativo gli stili e il DOM all'interno di un elemento personalizzato, Lit utilizza Shadow DOM. Shadow Roots genera un albero di ombre separato dalla struttura ad albero dei documenti principale. Ciò significa che la maggior parte degli stili è limitata a questo documento. Si sono verificati alcuni stili, come il colore e altri stili relativi ai caratteri.

Shadow DOM introduce inoltre nuovi concetti e selettori nella specifica 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.
   */
}

Stili di condivisione

Lit semplifica la condivisione degli stili tra i componenti sotto forma di CSSTemplateResults tramite i tag di modello css. Ad esempio:

// 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>`
  }
}

Applicazione tema

Le radici ombre rappresentano una sfida per i temi convenzionali, che in genere sono approcci ai tag in stile dall'alto verso il basso. Il modo convenzionale per affrontare la tematizzazione con i componenti web che utilizzano Shadow DOM è esporre un'API di stile tramite le proprietà personalizzate CSS. Ad esempio, questo è un pattern utilizzato da Material Design:

.mdc-textfield-outline {
  border-color: var(--mdc-theme-primary, /* default value */ #...);
}
.mdc-textfield--input {
  caret-color: var(--mdc-theme-primary, #...);
}

L'utente cambierà il tema del sito applicando i valori delle proprietà personalizzate:

html {
  --mdc-theme-primary: #F00;
}
html[dark] {
  --mdc-theme-primary: #F88;
}

Se i temi dall'alto verso il basso sono indispensabili e non riesci a esporre gli stili, puoi sempre disattivare DOM shadow sostituendo createRenderRoot per restituire this. In questo modo, il modello dei componenti verrà visualizzato nell'elemento personalizzato stesso anziché in una radice shadow collegata all'elemento personalizzato. Con questo andranno persi: l'incapsulamento degli stili, l'incapsulamento del DOM e gli slot.

Produzione

IE 11

Se devi supportare browser meno recenti come IE 11, dovrai caricare alcuni polyfill che escono per circa altri 33 kB. Ulteriori informazioni sono disponibili qui.

Bundle condizionali

Il team di Lit consiglia di pubblicare due diversi bundle, uno per IE 11 e uno per i browser moderni. Questo approccio offre diversi vantaggi:

  • La distribuzione di ES 6 è più rapida e serve alla maggior parte dei tuoi clienti
  • ES 5 traspilato aumenta significativamente le dimensioni dei bundle
  • I pacchetti agevolati ti offrono il meglio di entrambi i mondi
    • Supporto di IE 11
    • Nessun rallentamento sui browser moderni

Maggiori informazioni su come creare un bundle con pubblicazione condizionale sono disponibili sul nostro sito della documentazione qui.