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 in letteratura diversi concetti di reazione, ad esempio:

  • JSX e Creazione di modelli
  • Componenti e Accessori
  • Stato e Ciclo di vita
  • Hooks
  • Bambini
  • Riferimenti
  • Stato della mediazione

Cosa creerai

Al termine di questo codelab sarai in grado di convertire i concetti dei componenti React nei relativi analoghi Lit.

Che cosa ti serve

  • L'ultima versione di Chrome, Safari, Firefox o Edge.
  • Conoscenza di HTML, CSS, JavaScript e Chrome DevTools.
  • Conoscenza della reazione
  • (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 è molto piccola: si riduce a circa 5 kB minimizzati e compressi in confronto a React + ReactDOM oltre i 40 kB.

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 mettono a confronto il sistema di modelli di Lit, lit-html, con il VDOM di React, lit-html risulta più veloce dell'8-10% rispetto a React nel peggiore dei casi e più del 50% nei casi d'uso più comuni.

LitElement (classe base dei componenti di Lite) aggiunge un overhead minimo a lit-html, ma batte le prestazioni di React del 16-30% quando si confrontano funzionalità dei componenti come utilizzo della memoria e interazione e 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 sei pronto.

Con i moduli ES e le reti CDN moderne come Skypack o UNPKG, potresti non avere bisogno della gestione dei partner di rete per iniziare.

Tuttavia, se lo desideri, 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à in framework attuali e futuri. Se supporta 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 gli escape tratteggi tramite Refs, che non è una buona esperienza 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 suggerimenti Intelligence

Gli strumenti per sviluppatori sono integrati nel browser

I componenti Lit sono solo elementi HTML nel DOM. Ciò significa che, al fine di esaminare i componenti, non è necessario installare strumenti o estensioni per il browser.

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

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

È basato sul rendering lato server (SSR)

La Lit 2 è stata realizzata tenendo conto del supporto 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 i progressi del team Lit qui.

La partecipazione è bassa

Lit non richiede un impegno significativo da usare. 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 adattarsi autonomamente all'interno di un elemento Lit.

3. Configurazione e alla scoperta di Playground

Questo codelab può essere eseguito in due modi:

  • Puoi farlo interamente online, nel browser
  • (Avanzato) Puoi farlo sul tuo computer locale utilizzando VS Code

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://cdn.skypack.dev/lit';

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&#39;anteprima dell&#39;output come Sezione 3 e il pulsante di ricaricamento dell&#39;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
  • (Se TS) configura un file tsconfig rapido che generi moduli es ed es2015+
  • 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 dev 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 dev

4. JSX e Creazione di modelli

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

JSX e Modelli accesi

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, eseguire il rendering di Hello World JSX in questo modo:

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 precedente, sono presenti due elementi e un "nome" incluso. . 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 hanno bisogno di un frammento di reazione per raggruppare più elementi nei relativi modelli.

In Lit, i modelli sono racchiusi in un modello con tag LITeral html, che è da dove prende il nome.

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 valori TemplateResult lit possono accettare array, stringhe, altri TemplateResult e istruzioni.

Per un 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, prendi questo input React con 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.

Poi c'è un'associazione diretta all'attributo class anziché a className. È possibile aggiungere più associazioni all'attributo class, a meno che non utilizzi l'istruzione classMap che è un aiuto dichiarativo per attivare/disattivare le classi.

Infine, la proprietà value viene impostata sull'input. A differenza di React, l'elemento di input non viene impostato come di sola lettura, in quanto segue l'implementazione e il comportamento nativi 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 come disabled="false" viene comunque letto come true dal DOM perché inputElement.hasAttribute('disabled') === true
html`<my-element .property-name=${anyVar}>`;
  • Il prefisso . è la sintassi di associazione per impostare 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}>`;
  • Associato all'attributo di un elemento
  • Equivalente a inputRef.setAttribute('attribute-name', stringVar)
  • Ideale 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 precedente, viene definito un input che fa quanto segue:

  • Registra la parola "clic" quando l'input viene fatto clic
  • 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 di Lit, è presente un listener aggiunto all'evento click con @click.

Poi, invece di utilizzare onChange, è presente un'associazione all'evento input nativo di <input> poiché l'evento change 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 associazione per un listener di eventi
  • Equivalente a inputRef.addEventListener('event-name', ...)
  • Utilizza nomi di eventi DOM nativi

5. Componenti e Accessori

In questa sezione imparerai a conoscere i componenti e le funzioni delle classi Lit. Lo stato e gli hook sono trattati in maggiore dettaglio nelle sezioni successive.

Componenti della classe e LitElement

L'equivalente Lit di un componente di una classe React è LitElement e il concetto di "proprietà reattive" di Lit è una combinazione degli oggetti e dello 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 faresti 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à sul 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 JavaScript del decorator 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 di funzione in quanto 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 si otterrebbe:

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" è 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 agli oggetti di scena e allo stato di React, ma mutevole
  • 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 è molto simile a quello di React, ma ci sono alcune differenze notevoli.

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 Lit è 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 ecc.
  • Come per React, render() deve essere una funzione pura
  • Il rendering verrà eseguito sul 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 è connesso, 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 causano un nuovo rendering, anche se il browser in genere 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 i listener di eventi che sono stati ripuliti alla 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 letterale è updated (con il passato inglese di "update")
  • A differenza di React, updated viene chiamato anche nella visualizzazione 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);
}
  • Lit equivalente è simile a disconnectedCallback
  • A differenza dei componenti React, quando gli elementi personalizzati vengono scollegati dal DOM il componente non viene eliminato.
  • 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 la pulizia dei listener di eventi e dei riferimenti leaky in modo che il browser possa garbage collect il 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 il messaggio "Hello World! È" e poi mostra l'ora
  • L'orologio viene aggiornato ogni secondo
  • Quando il dispositivo è smontato viene cancellato l'intervallo che chiama il segno di spunta.

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

Quindi, inizializza date e dichiara che è una proprietà reattiva interna con @state, poiché gli utenti del componente non imposteranno direttamente date.

// Lit (TS)
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';

@customElement('lit-clock')
class LitClock extends LitElement {
  @state() // declares internal reactive prop
  private date = new Date(); // initialization
}

// Lit (JS)
import {LitElement, html} from 'lit';

class LitClock extends LitElement {
  static get properties() {
    return {
      // declares internal reactive prop
      date: {state: true}
    }
  }

  constructor() {
    super();
    // initialization
    this.date = new Date();
  }
}

customElements.define('lit-clock', LitClock);

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

Ora vediamo l'implementazione di componentDidMount. Anche in questo caso, l'analogico Lit è una miscela di firstUpdated e connectedCallback. Nel caso di questo componente, la chiamata di tick con setInterval non richiede l'accesso al DOM all'interno della radice. Inoltre, l'intervallo verrà cancellato quando l'elemento viene rimosso dalla struttura ad albero dei documenti, quindi se viene ricollegato, l'intervallo dovrà essere riavviato. Di conseguenza, 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, pulisci l'intervallo in modo che non esegua il segno di spunta dopo la disconnessione dell'elemento dalla struttura ad albero dei documenti.

// 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 in letteratura i concetti di React Hook.

I concetti degli hook di React

Gli hook di reazione consentono di "agganciare" i componenti della funzione in uno 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 sulle funzioni ha risolto alcuni problemi con la sintassi basata sulle classi di React, come:

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

Reagisci i concetti degli hook in Lit

Come indicato nella sezione Componenti e Props, 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, fa riferimento 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 di oggetti che può essere agganciata al ciclo di vita di aggiornamento di un host del controller come LitElement.

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

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

Creando un controller reattivo e collegandolo a un host con addController, il ciclo di vita del controller verrà chiamato insieme a quello dell'host. Ad esempio, richiama l'esempio di orologio dalla sezione State & Sezione 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 il messaggio "Hello World! È" e poi mostra l'ora
  • L'orologio viene aggiornato ogni secondo
  • Quando il dispositivo è smontato viene cancellato l'intervallo che chiama il segno di spunta.

Creazione dello scaffolding dei componenti

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 nella maggior parte dei casi basilari.

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 dispositivo hostDisconnected del controller

Per ulteriori informazioni sulla traduzione del ciclo di vita React in quello 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 illustrato nell'esempio in 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);
  }
}

Utilizzo del 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 del nuovo rendering nel controller

Tieni presente che verrà visualizzata l'ora, ma non viene aggiornata. Questo perché il controller imposta la data ogni secondo, ma l'host non si aggiorna. Questo perché date cambierà nella classe ClockController e non più nel componente. Ciò significa che dopo aver impostato date sul controller, all'host deve essere richiesto 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 stringere!

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 bambini in Lit.

Slot e Figli

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 allo stesso modo di React. In Lit, gli elementi secondari sono elementi HTMLElement associati alle aree annuncio. Questo allegato si chiama Proiezione.

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

Più slot

In React, aggiungere più slot è essenzialmente la stessa cosa che ereditare più oggetti.

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 state definite più aree con l'attributo name: <slot name="slot-name">. Ciò consente ai publisher secondari di dichiarare quale area verrà assegnata.

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

Assegna elementi secondari agli slot

In React, gli elementi secondari vengono assegnati alle aree tramite le proprietà di un componente. Nell'esempio seguente, gli elementi React vengono passati agli oggetti 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 un'area predefinita (ad es. <slot>) e non esiste un'area con un attributo name (ad es. <slot name="foo">) corrispondente 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, imparerai ad acquisire riferimenti a elementi in Lit.

Riferimenti delle reazioni

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 ed esegue il rendering di HTMLElements.

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:

  • Visualizza un input di testo vuoto e un pulsante con testo
  • Imposta lo stato attivo sull'input quando viene fatto clic sul pulsante

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

Acceso "Riferimenti" con @query

È vicino al browser e crea una sottile astrazione delle 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 quanto segue:

  • 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 a mediare lo stato tra i componenti in Lit.

Componenti riutilizzabili

La reazione simula le pipeline di rendering funzionale con un flusso di dati dall'alto verso il basso. I genitori li informano dei loro figli attraverso gli oggetti di scena. I bambini comunicano con i genitori attraverso richiami all'interno degli oggetti 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 precedente, 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. È possibile creare un componente MyCounter 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. Nota come addToCounter non viene trasmesso 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>
    `;
  }
}

Nell'esempio precedente:

  • Crea una proprietà reattiva denominata count che aggiorna il componente quando il valore viene modificato
  • Associa 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 norma utilizzare il sistema di eventi del browser per mostrare i dettagli dal basso verso l'alto.

Questo approccio segue le best practice e rispetta l'obiettivo di Lit di fornire supporto multipiattaforma per i componenti web.

11. Stili

In questa sezione viene illustrato come usare lo stile in Lit.

Stili

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

Stili in linea

Lit supporta gli stili incorporati e l'associazione a questi stili.

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 precedente sono presenti 2 intestazioni ciascuna con uno stile in linea.

Ora importa e associa 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

L'istruzione styleMap semplifica l'utilizzo di JavaScript per l'impostazione di stili incorporati. 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 sopra fa quanto segue:

  • Mostra un elemento h1 con un bordo e un selettore colori
  • Modifica border-color nel valore del selettore colori

Inoltre, è disponibile styleMap, che viene utilizzato per impostare gli stili di h1. styleMap segue una sintassi simile a quella dell'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:

  • Analizzato una volta per classe e 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 definisce l'ambito dei componenti stili alle loro radici. 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 Creazione di modelli

Acceso e DOM virtuale

Lit-html non include un DOM virtuale convenzionale che differenzia ogni singolo nodo. Utilizza invece funzionalità relative alle prestazioni intrinseche alla specifica del valore letterale del modello con tag di ES2015. I valori letterali del modello con tag sono stringhe letterali di modello a cui sono collegate funzioni tag.

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.

Gran parte delle prestazioni straordinarie in Lit deriva dal fatto che gli array di stringhe passati alla funzione tag hanno lo stesso puntatore (come mostrato nel secondo console.log). Il browser non ricrea un nuovo array strings su ogni chiamata di funzione di 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 integrato nel browser dei valori letterali dei modelli taggati offre a Lit un 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 i valori letterali dei modelli con tag HTML con React o Preact, il team di Lit consiglia la raccolta htm.

Tuttavia, come nel caso del sito Google Codelabs e di diversi editor di codice online, noterai che l'evidenziazione della sintassi letterale dei modelli con tag non è molto comune. Alcuni IDE ed editor di testo li supportano per impostazione predefinita, ad esempio Atom e l'evidenziazione dei blocchi di 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.

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

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 con Lit, tutto ciò che serve è 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

La reazione utilizza un sistema di eventi sintetici. Ciò significa che reazione-dom deve definire ogni evento che verrà utilizzato su ogni componente e fornire un listener 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 da parte degli sviluppatori mediocre durante l'integrazione di librerie che non hanno in mente React e comporta la scrittura di un wrapper specifico per React.

Lit-html accede direttamente al DOM e utilizza gli eventi nativi, quindi l'aggiunta di listener di eventi è facile come @event-name=${eventNameListener}. 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 della reazione e 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).

Il sistema di componenti degli elementi personalizzati offre alcuni vantaggi:

  • È nativo del browser e non richiede strumenti
  • Si integra in ogni API del browser dalle innerHTML e dalle document.createElement alle querySelector
  • In genere possono essere utilizzati in più framework
  • Può essere registrato in modo lento con customElements.define e "hydrate" DOM

Alcuni svantaggi degli elementi personalizzati rispetto ai componenti di React:

  • Impossibile creare un elemento personalizzato senza definire una classe (quindi nessun componente funzionale simile a JSX)
  • Deve contenere un tag di chiusura
    • Nota: nonostante la comodità dello sviluppatore, i fornitori di browser tendono a pentirsi delle specifiche dei tag a chiusura automatica, motivo per cui le specifiche più recenti tendono a non includere tag che si chiudono automaticamente
  • 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 dei lit-ssr hanno superato i problemi principali della registrazione a JavaScript. Inoltre, alcune aziende come GitHub sfruttano la registrazione lazy con elementi personalizzati per migliorare progressivamente le pagine con un tocco facoltativo.

Trasmettere dati a elementi personalizzati

Un equivoco comune con gli elementi personalizzati è che i dati possono essere trasmessi solo come stringhe. Probabilmente questo equivoco deriva dal fatto che gli attributi degli elementi possono essere scritti solo come stringhe. Sebbene sia vero che Lit trasmetterà gli attributi stringa ai 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 convertirà il valore della stringa di un attributo in un number, dopodiché viene introdotta una struttura di dati complessa con attribute:false che disattiva la gestione degli attributi di Lit.

Ecco come trasferire i 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 React

static getDerivedStateFromProps

Non esiste un equivalente in Lit poiché props e stato hanno le stesse proprietà di classe

shouldComponentUpdate

  • L'equivalente in Lit è shouldUpdate
  • Richiamato al primo rendering a differenza della reazione
  • Funzione simile a shouldComponentUpdate della reazione

getSnapshotBeforeUpdate

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

willUpdate

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

update

  • Chiamata effettuata dopo le ore 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
  • Ottima posizione per acquisire informazioni dal DOM che circonda il componente prima che venga eseguito il commit dell'output visualizzato nel DOM
  • Questo metodo non viene chiamato sul server in SSR

Altri callback di ciclo di vita Lit

Esistono diversi callback del ciclo di vita che non sono stati menzionati nella sezione precedente perché non esistono analogie in React. Questi 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 un documentFragment di HTMLTemplateElement al document principale. Anche questo callback fa parte 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 contribuire 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 la risoluzione di updateComplete. Si tratta di un problema comune quando un componente esegue il rendering di un componente secondario e i relativi cicli di rendering devono essere sincronizzati. ad es.

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

performUpdate

Questo metodo è quello che chiama i callback del ciclo di vita dell'aggiornamento. In genere questa operazione non dovrebbe essere necessaria, ad eccezione dei 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

In base alla specifica degli elementi personalizzati, questa proprietà sarà true se l'elemento è attualmente collegato alla struttura ad albero dei documenti principale.

Visualizzazione ciclo di vita aggiornamento acceso

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

  • Prima dell'aggiornamento
  • Aggiorna
  • Dopo l'aggiornamento

Pre-aggiornamento

Un grafo diretto aciclico di nodi con nomi di callback. costruttore per requestUpdate. da @property al setter proprietà. attributeChangedCallback in parametro di impostazione della proprietà. Proprietà impostata su hasChanged. hasChanged su requestUpdate. requestUpdate rimanda al grafico successivo, ovvero l&#39;aggiornamento del ciclo di vita.

Dopo il giorno requestUpdate, è in attesa un aggiornamento programmato.

Aggiorna

Un grafo diretto aciclico di nodi con nomi di callback. Freccia dall&#39;immagine precedente dei punti del ciclo di vita precedenti all&#39;aggiornamento per performUpdate. di eseguire l&#39;aggiornamento a ifUpdate. Il comando deveUpdate rimanda sia a &quot;complete update if false&quot; sia a willUpdate. si aggiornerà per eseguire l&#39;aggiornamento. eseguire l&#39;aggiornamento sia al rendering sia al grafico del ciclo di vita successivo all&#39;aggiornamento. il rendering rimanda anche al grafico del ciclo di vita successivo all&#39;aggiornamento.

Dopo l'aggiornamento

Un grafo diretto aciclico di nodi con nomi di callback. Freccia dall&#39;immagine precedente dei punti del ciclo di vita dell&#39;aggiornamento a firstUpdated. firstUpdated to updated. è stato aggiornato 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 delle funzioni con hook tendono a essere molto più semplici e più leggibili delle controparti dei componenti di classe. Tuttavia, quando si introducono aggiornamenti di stato asincroni e si passano dati tra hook o effetti, il pattern degli hook tende a non essere sufficiente e una soluzione basata su classi come i controller reattivi tende a brillare.

hook di richieste API e controller

È 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
    • Visualizza la risposta di useAPI
      • ID utente + Nome utente
      • Messaggio di errore
        • 404 quando raggiunge l'utente 11 (per progettazione)
        • Errore di interruzione se il recupero dell'API viene interrotto
      • Caricamento del messaggio
    • Visualizza un pulsante di azione
      • Utente successivo: che recupera l'API per l'utente successivo
      • Annulla: interrompe il recupero dell'API e viene visualizzato un errore
  • useApi.tsx
    • Definisce un hook personalizzato useApi
    • Recuperarà in modo asincrono un oggetto utente da un'API
    • Emissioni:
      • Nome utente
      • Se il recupero è in fase di caricamento
      • 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 come gli 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 React con gli hook, ma lo stesso ambiente senza build 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 all'area senza nome predefinita. Nell'esempio seguente, MyApp inserirà un paragrafo in un'area denominata. L'altro paragrafo verrà impostato automaticamente sullo 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 listener di eventi a un evento slotchange. Nell'esempio seguente, il primo slot trovato in shadowRoot verrà registrato con assignedNodes 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

Lit e React espongono entrambi un riferimento a un HTMLElement dopo che sono state chiamate le rispettive funzioni render. Tuttavia, vale la pena rivedere come React e Lit compongono il DOM, che viene poi restituito tramite un decoratore di Lit @query o un riferimento di React.

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

Dopo aver convertito un componente React in HTMLElement, ReactDOM cerca un attributo denominato ref in ReactComponent. Se disponibile, ReactDOM posiziona il riferimento di HTMLElement a ref.current.

LitElement usa la funzione tag modello html di lit-html per comporre un elemento modello dietro le quinte. 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 una 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 decorator @queryAsync è più adatto a 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 funziona 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 stili e 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 anche nuovi concetti e selettori nelle specifiche 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 nel formato CSSTemplateResults mediante i tag del 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 motivo utilizzato in Material Design:

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

L'utente dovrebbe quindi modificare il tema del sito applicando 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 Shadow DOM eseguendo l'override di createRenderRoot per restituire this, che a sua volta eseguirà il rendering dei componenti. modello all'elemento personalizzato stesso anziché a 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.

Pacchetti 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 condizionali offrono il meglio delle due modalità
    • 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.