Qu'est-ce que Lit ?
Lit est un ensemble de bibliothèques Open Source créées par Google, qui aide les développeurs à concevoir des composants légers et rapides qui fonctionnent dans n'importe quel framework. Il vous permet de créer, entre autres, des applications, des systèmes de conception et des composants partageables.
Ce que vous allez apprendre
Comment convertir les concepts de React ci-dessous dans Lit :
- JSX et création de modèles
- Composants et props
- État et cycle de vie
- Hooks
- Enfants
- Refs
- État de médiation
Ce que vous allez faire
À la fin de cet atelier de programmation, vous saurez adapter les concepts de composants React à ceux similaires dans Lit.
Ce dont vous avez besoin
- Dernière version de Chrome, Safari, Firefox ou Edge.
- Connaissances des langages HTML, CSS, JavaScript et des Outils pour les développeurs Chrome.
- Connaissances sur la bibliothèque React.
- (Niveau avancé) Pour une expérience de développement optimale, téléchargez VS Code. Vous aurez aussi besoin du plug-in lit pour VS Code et npm.
Les concepts et capacités de base de Lit sont semblables à ceux de React, à bien des égards, mais Lit présente aussi des différences et facteurs de différenciation non négligeables :
Peu volumineux
Comparé aux plus de 40 ko de React+ReactDOM, Lit ne fait environ que 5 ko une fois compressé au format .gz.
Plus rapide
Selon des analyses comparatives publiques entre lit-html (le système de création de modèles de Lit) et le DOM virtuel de React, lit-html est 8 à 10 % plus rapide que React dans le pire des cas et plus de 50 % plus rapide dans les cas d'utilisation les plus courants.
LitElement (la classe de base des composants de Lit) ajoute une surcharge minimale à lit-html, mais est 16 à 30 % plus performant que React si l'on compare les fonctionnalités des composants, comme l'utilisation de mémoire, le temps de démarrage, et la date et l'heure de l'interaction.
Aucun compilation requise
Avec les nouvelles fonctionnalités du navigateur, comme les modules ES et les littéraux de modèles balisés, Lit n'exige aucune compilation pour s'exécuter. Autrement dit, les environnements de développement peuvent être configurés avec une balise de script, un navigateur et un serveur. Voilà juste ce dont vous avez besoin pour être opérationnel.
Grâce aux modules ES et aux CDN récents tels que Skypack ou UNPKG, vous n'aurez peut-être pas besoin de npm pour démarrer.
Toutefois, si vous le souhaitez, vous pouvez quand même compiler et optimiser le code Lit. La récente consolidation des développeurs concernant les modules ES natifs a été bénéfique pour Lit : Lit est seulement un code JavaScript normal qui ne nécessite pas de CLI spécifiques au framework ni de gérer les compilations.
Compatibilité avec plusieurs frameworks
Les composants de Lit s'appuient sur un ensemble de normes Web, appelées "Web Components". Autrement dit, la création d'un composant dans Lit fonctionnera dans les frameworks actuels et futurs. Et si les éléments HTML sont acceptés, les Web Components le sont également.
Le seul problème avec l'interopérabilité des frameworks est lorsque leur compatibilité avec le DOM est limitée. Bien que React soit l'un de ces frameworks, il propose aux développeurs des attributs ref (ou "refs") qui font office de "trappes de secours" sans être l'idéal.
L'équipe Lit a travaillé sur un projet expérimental appelé @lit-labs/react
, qui analyse automatiquement vos composants Lit et génère un wrapper React afin que vous n'ayez pas à utiliser de refs.
Un rapport Custom Elements Everywhere vous indique également les frameworks et bibliothèques compatibles avec des éléments personnalisés.
TypeScript comme langage de programmation de première classe
Même si vous pouvez écrire tout votre code Lit en JavaScript, Lit est écrit en TypeScript (que l'équipe Lit recommande d'ailleurs aux développeurs).
L'équipe Lit a collaboré avec la communauté Lit pour aider à maintenir les projets qui intègrent la validation de types et la saisie semi-automatique du code (IntelliSense) dans TypeScript aux modèles Lit lors des phases de développement et de compilation avec lit-analyzer
et lit-plugin
.
Outils pour les développeurs Chrome intégrés au navigateur
Les composants Lit sont de simples éléments HTML dans le DOM. Ainsi, pour inspecter vos composants, vous n'avez pas besoin d'installer d'outils ni d'extensions pour votre navigateur.
Il vous suffit d'accéder aux outils pour les développeurs, de sélectionner un élément et d'explorer ses propriétés ou son état.
, que $0.value renvoie hello world, que $0.outlined renvoie true, et que {$0} affiche l'expansion de la propriété" class="l10n-relative-url-src" l10n-attrs-original-order="alt,src,class" src="https://codelabs.developers.google.com/codelabs/lit-2-for-react-devs/./img/browser-tools.png" />
Rendu côté serveur
Lit 2 a été conçu pour prendre en charge le rendu côté serveur. Au moment de la rédaction de cet atelier de programmation, l'équipe Lit n'a pas encore publié les outils de rendu côté serveur sous une forme stable. Toutefois, elle a déjà déployé des composants rendus côté serveur dans l'ensemble des produits Google. Elle compte aussi publier prochainement ces outils en externe sur GitHub.
En attendant, vous pouvez suivre les avancées de l'équipe Lit en cliquant ici.
Flexibilité
Lit n'impose aucun grand engagement. Vous pouvez créer des composants dans Lit et les ajouter à votre projet actuel. S'ils ne vous conviennent pas, vous n'avez pas besoin de convertir tout de suite l'application entière, car les composants Web fonctionnent dans d'autres frameworks.
Vous avez développé une application complète dans Lit et vous voulez passer à autre chose ? Vous pouvez la placer dans votre nouveau framework et migrer tout ce que vous voulez vers les composants de ce framework.
En outre, de nombreux frameworks récents prennent en charge les résultats dans les composants Web, ce qui signifie qu'ils peuvent généralement s'intégrer eux-mêmes à un élément Lit.
Vous pouvez suivre cet atelier de programmation de deux façons :
- Entièrement en ligne, dans votre navigateur
- (Niveau avancé) Sur votre ordinateur local à l'aide de VS Code
Accéder au code
Tout au long de cet atelier de programmation, vous trouverez des liens vers le terrain de jeu Lit, comme suit :
Le terrain de jeu est un bac à sable de code qui s'exécute complètement dans votre navigateur. Il peut compiler et exécuter des fichiers TypeScript et JavaScript, et aussi résoudre automatiquement les importations dans les modules de nœud. Par exemple :
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://cdn.skypack.dev/lit';
Vous pouvez suivre l'intégralité du tutoriel dans le terrain de jeu Lit, en utilisant ces points de contrôle comme points de départ. Si vous utilisez VS Code, vous pouvez vous servir de ces points de contrôle pour télécharger le code de départ de n'importe quelle étape ou pour vérifier votre travail.
Explorer l'UI du terrain de jeu Lit
La capture d'écran de l'UI du terrain de jeu Lit montre les sections que vous allez utiliser dans cet atelier de programmation.
- Sélecteur de fichiers (notez la présence du bouton Plus)
- Éditeur de fichier
- Aperçu du code
- Bouton d'actualisation
- Bouton de téléchargement
Configuration de VS Code (niveau avancé)
Voici les avantages que présente cette configuration de VS Code :
- Validation du type de modèle
- Saisie semi-automatique du code du modèle (IntelliSense)
Si npm et VS Code (avec le plug-in lit) sont déjà installés et que vous savez comment utiliser cet environnement, vous pouvez simplement télécharger et lancer ces projets comme suit :
- Appuyez sur le bouton de téléchargement.
- Extrayez le contenu du fichier .tar dans un répertoire.
- (Si TypeScript) Configurez rapidement un fichier tsconfig qui génère des modules ES et ES2015+.
- Installez un serveur de développement capable de résoudre des spécificateurs de module nus (l'équipe Lit recommande @web/dev-server).
- Exécutez ce serveur et ouvrez votre navigateur (si vous employez @web/dev-server, vous pouvez utiliser
web-dev-server --node-resolve --watch --open
).
Dans cette section, vous allez découvrir les bases de la création de modèles dans Lit.
JSX et modèles Lit
JSX est une extension syntaxique de JavaScript, qui permet aux utilisateurs de React d'écrire facilement des modèles dans leur code JavaScript. Les modèles Lit ont un objectif similaire : exprimer l'UI d'un composant en tant que fonction de son état.
Syntaxe de base
Dans React, vous afficheriez un Hello World JSX comme suit :
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
);
Dans l'exemple ci-dessus, il y a deux éléments et une variable "name" incluse. Dans Lit, vous procéderiez comme suit :
import {html, render} from 'lit';
const name = 'Josh Perez';
const element = html`
<h1>Hello, ${name}</h1>
<div>How are you?</div>`;
render(
element,
mountNode
);
Notez que les modèles Lit n'ont pas besoin de composant React.Fragment pour regrouper plusieurs éléments dans ses modèles.
Dans Lit, les modèles sont encapsulés avec un LITtéral de modèle balisé html
(d'où le nom "Lit").
Valeurs des modèles
Les modèles Lit peuvent accepter d'autres modèles Lit, appelés TemplateResult
. Par exemple, encapsulez name
dans des balises mise en italique (<i>
) et encapsulez-le avec un littéral de modèle balisé N.B. Veillez à utiliser l'accent grave (`
) et non le guillemet simple ('
).
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
);
Dans Lit, les classes TemplateResult
peuvent accepter des tableaux, des chaînes, d'autres classes TemplateResult
, ainsi que des directives.
Pour faire un test, essayez de convertir le code React suivant dans 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
);
Réponse :
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
);
Transmission et définition de props
L'une des principales différences entre les syntaxes JSX et Lit est la syntaxe de liaison de données. Prenons l'exemple de cette entrée React avec liaisons :
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
);
Dans l'exemple ci-dessus, une entrée effectue ce qui suit :
- Elle désactive une variable (dans ce cas, en la définissant sur "false").
- Elle définit la classe sur
static-class
, ainsi qu'une variable (dans ce cas,"static-class my-class"
). - Elle définit une valeur par défaut.
Dans Lit, vous procéderiez comme suit :
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
);
Dans l'exemple Lit, une liaison booléenne est ajoutée pour activer/désactiver l'attribut disabled
.
Ensuite, une liaison directe est effectuée avec l'attribut class
au lieu de className
. Vous pouvez ajouter plusieurs liaisons à l'attribut class
, sauf si vous utilisez la directive classMap
(une aide déclarative pour activer/désactiver des classes).
Enfin, la propriété value
est définie sur l'entrée. Contrairement à React, l'élément d'entrée ne sera pas en lecture seule, car il suit l'implémentation et le comportement natifs de l'entrée.
Syntaxe de liaison de props dans Lit
html`<my-element ?attribute-name=${booleanVar}>`;
- Le préfixe
?
correspond à la syntaxe de liaison pour activer/désactiver un attribut sur un élément. - Équivalent à
inputRef.toggleAttribute('attribute-name', booleanVar)
. - Utile pour les éléments qui utilisent
disabled
, cardisabled="false"
est toujours lu comme "true" par le DOM en raison deinputElement.hasAttribute('disabled') === true
.
html`<my-element .property-name=${anyVar}>`;
- Le préfixe
.
correspond à la syntaxe de liaison pour définir une propriété d'un élément. - Équivalent à
inputRef.propertyName = anyVar
. - Idéal pour transmettre des données complexes, comme des objets, tableaux ou classes.
html`<my-element attribute-name=${stringVar}>`;
- Liaison à l'attribut d'un élément.
- Équivalent à
inputRef.setAttribute('attribute-name', stringVar)
. - Idéal pour les valeurs de base, les sélecteurs de règles de style et les querySelectors.
Transmission de gestionnaires
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
);
Dans l'exemple ci-dessus, une entrée effectue ce qui suit :
- Consignation du mot "clic" quand l'utilisateur clique sur l'entrée
- Consignation de la valeur de l'entrée quand l'utilisateur saisit un caractère
Dans Lit, vous procéderiez comme suit :
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
);
Dans l'exemple Lit, un écouteur est ajouté à l'événement click
avec @click
.
Ensuite, au lieu d'utiliser onChange
, il y a une liaison à l'événement input
natif de <input>
, car l'événement change
natif se déclenche uniquement sur blur
(React résume ces événements).
Syntaxe du gestionnaire d'événements dans Lit
html`<my-element @event-name=${() => {...}}></my-element>`;
- Le préfixe
@
correspond à la syntaxe de liaison d'un écouteur d'événements. - Équivalent à
inputRef.addEventListener('event-name', ...)
. - Utilise le nom des événements DOM natifs.
Dans cette section, vous allez étudier les composants à base de classes et les fonctions composants Lit. L'état et les hooks sont traités plus en détail dans les sections suivantes.
Composants à base de classes et LitElement
LitElement est l'équivalent dans Lit d'un composant à base de classe React, et le concept de "propriétés réactives" de Lit combine l'état et les props de React. Exemple :
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
);
Dans l'exemple ci-dessus, un composant React effectue ce qui suit :
- Il affiche un
name
. - Il définit la valeur par défaut de
name
sur une chaîne vide (""
). - Il réaffecte
name
à"Elliott"
.
Voici comment vous feriez cela dans LitElement.
Dans 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>`
}
}
Dans 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);
Et dans le fichier HTML :
<!-- index.html -->
<head>
<script type="module" src="./index.js"></script>
</head>
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
Examen de ce qui se passe dans l'exemple ci-dessus :
@property({type: String})
name = '';
- Définit une propriété réactive publique (une partie de l'API publique de votre composant)
- Affiche un attribut (par défaut) ainsi qu'une propriété sur votre composant
- Indique comment convertir l'attribut du composant (chaînes) en une valeur
static get properties() {
return {
name: {type: String}
}
}
- Cette propriété remplit la même fonction que le décorateur TypeScript
@property
, mais s'exécute en mode natif dans JavaScript.
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
- Ceci est appelé chaque fois qu'une propriété réactive est modifiée.
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
...
}
- Un nom de balise d'un élément HTML est ainsi associé à une définition de classe.
- En raison des règles liées aux éléments personnalisés, le nom de balise doit inclure un tiret (-).
this
dans un LitElement fait référence à l'instance de l'élément personnalisé (<welcome-banner>
, dans ce cas).
customElements.define('welcome-banner', WelcomeBanner);
- Il s'agit de l'équivalent JavaScript du décorateur TypeScript
@customElement
.
<head>
<script type="module" src="./index.js"></script>
</head>
- La définition de l'élément personnalisé est importée.
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
- L'élément personnalisé est ajouté à la page.
- La propriété
name
est définie sur'Elliott'
.
Fonctions composants
Lit n'a pas une interprétation individuelle d'une fonction composant, car il n'utilise ni JSX, ni un préprocesseur. Toutefois, il est assez simple de composer une fonction qui utilise des propriétés et affiche un DOM en fonction de ces propriétés. Exemple :
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Elliott"/>
ReactDOM.render(
element,
mountNode
);
Voici comment cela serait dans Lit :
import {html, render} from 'lit';
function Welcome(props) {
return html`<h1>Hello, ${props.name}</h1>`;
}
render(
Welcome({name: 'Elliott'}),
document.body.querySelector('#root')
);
Dans cette section, vous allez étudier l'état et le cycle de vie dans Lit.
État
Le concept de "propriétés réactives" dans Lit allie l'état et les props de React. Lorsqu'elles sont modifiées, ces propriétés peuvent déclencher le cycle de vie du composant. Elles se déclinent en deux variantes :
Propriétés réactives publiques
// 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';
}
- Propriété définie par
@property
- Semblable aux props et à l'état dans React (mais modifiable)
- API publique accessible et définie par les utilisateurs du composant
État réactif interne
// 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';
}
- Propriété définie par
@state
- Semblable à l'état dans React (mais modifiable)
- État interne privé accessible généralement depuis le composant ou les sous-classes
Cycle de vie
Le cycle de vie dans Lit est assez semblable à celui dans React. Toutefois, il y a quelques différences notables.
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';
}
}
- Équivalent dans Lit :
constructor
- Inutile de transmettre quelque chose au super appel
- Appelé par (pas totalement inclusif) :
document.createElement
document.innerHTML
new ComponentClass()
- Si un nom de balise non mis à jour figure sur la page, et que la définition est chargée et enregistrée auprès de
@customElement
ou decustomElements.define
- Fonction semblable à
constructor
dans React
render
// React
render() {
return <div>Hello World</div>
}
// Lit
render() {
return html`<div>Hello World</div>`;
}
- Équivalent dans Lit :
render
- Peut renvoyer n'importe quel résultat affichable (ex. :
TemplateResult
,string
, etc.) - Semblable à celle dans React,
render()
doit être une fonction pure - Rendu à n'importe quel nœud renvoyé par
createRenderRoot()
(ShadowRoot
par défaut)
componentDidMount
componentDidMount
est semblable à une combinaison des rappels de cycle de vie firstUpdated
et connectedCallback
dans Lit.
firstUpdated
import Chart from 'chart.js';
// React
componentDidMount() {
this._chart = new Chart(this.chartElRef.current, {...});
}
// Lit
firstUpdated() {
this._chart = new Chart(this.chartEl, {...});
}
- Appelé la première fois que le modèle du composant est affiché dans la racine du composant
- Ne sera appelé que si l'élément est connecté (par exemple, non appelé via
document.createElement('my-component')
tant que ce nœud n'est pas ajouté à l'arborescence DOM) - Bon endroit pour la configuration du composant qui nécessite que le DOM soit affiché par le composant
- Contrairement à
componentDidMount
dans React, les modifications apportées aux propriétés réactives dansfirstUpdated
entraînent un nouveau rendu, bien que le navigateur regroupe généralement les modifications dans le même frame. Si ces modifications n'exigent pas d'accéder au DOM de la racine, elles doivent généralement figurer danswillUpdate
connectedCallback
// React
componentDidMount() {
this.window.addEventListener('resize', this.boundOnResize);
}
// Lit
connectedCallback() {
super.connectedCallback();
this.window.addEventListener('resize', this.boundOnResize);
}
- Appelé chaque fois que l'élément personnalisé est inséré dans l'arborescence DOM
- Contrairement aux composants React, lorsque des éléments personnalisés sont dissociés du DOM, ils ne sont pas détruits et ils peuvent ainsi être "connectés" plusieurs fois
- Utile pour réinitialiser le DOM ou associer de nouveau des écouteurs d'événements qui ont été effacés lors de la déconnexion
- Remarque :
connectedCallback
peut être appelé avantfirstUpdated
. Ainsi, lors du premier appel, le DOM peut ne pas être disponible
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);
}
}
- Équivalent dans Lit :
updated
(avec "update" au passé en anglais) - Contrairement à React,
updated
est également appelé lors du rendu initial - Fonction semblable à
componentDidUpdate
dans React
componentWillUnmount
// React
componentWillUnmount() {
this.window.removeEventListener('resize', this.boundOnResize);
}
// Lit
disconnectedCallback() {
super.disconnectedCallback();
this.window.removeEventListener('resize', this.boundOnResize);
}
- Équivalent dans Lit semblable à
disconnectedCallback
. - Contrairement aux composants React, si les éléments personnalisés sont dissociés du DOM, le composant n'est pas détruit.
- Contrairement à
componentWillUnmount
,disconnectedCallback
est appelé après que l'élément est supprimé de l'arborescence. - Le DOM à l'intérieur de la racine est quand même associé à la sous-arborescence de la racine.
- Utile pour effacer les écouteurs d'événements et les références qui fuient afin que le navigateur puisse récupérer le composant.
Exercice
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')
);
Dans l'exemple ci-dessus, une horloge simple effectue ce qui suit :
- Elle affiche "Hello World ! Il est…", suivi de l'heure.
- L'horloge est mise à jour toutes les secondes.
- Lorsque le composant est démonté, l'intervalle qui appelle la méthode tick est effacé.
Commencez par la déclaration de la classe du composant :
// 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);
Ensuite, initialisez la date
et déclarez-la comme propriété réactive interne avec @state
, car les utilisateurs du composant ne définiront pas directement la 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);
Affichez ensuite le modèle.
// Lit (JS & TS)
render() {
return html`
<div>
<h1>Hello, World!</h1>
<h2>It is ${this.date.toLocaleTimeString()}.</h2>
</div>
`;
}
Implémentez maintenant la méthode tick.
tick() {
this.date = new Date();
}
Vient ensuite l'implémentation de componentDidMount
. Encore une fois, l'équivalent dans Lit combine firstUpdated
et connectedCallback
. Dans le cas de ce composant, il n'est pas nécessaire d'accéder au DOM à la racine pour appeler la méthode tick
avec setInterval
. De plus, l'intervalle est effacé une fois l'élément supprimé de l'arborescence de documents. Par conséquent, si vous l'associez de nouveau, l'intervalle devra redémarrer. Il est donc préférable d'utiliser connectedCallback
.
// Lit (TS)
@customElement('lit-clock')
class LitClock extends LitElement {
@state()
private date = new Date();
private timerId = -1; // initialize timerId for TS
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
);
}
Enfin, effacez l'intervalle de sorte qu'il n'exécute pas la méthode tick une fois l'élément détaché de l'arborescence de documents.
// Lit (TS & JS)
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
En regroupant tout, cela devrait se présenter comme suit :
// 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;
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);
Dans cette section, vous allez apprendre à convertir les concepts de hooks React dans Lit.
Concepts des hooks React
Dans React, les hooks permettent aux fonctions composants de "s'accrocher" à un état. Ils présentent plusieurs avantages.
- Ils permettent de réutiliser facilement la logique avec état.
- Ils aident à diviser un composant en fonctions plus petites.
De plus, l'accent mis sur les fonctions composants a permis de résoudre certains problèmes liés à la syntaxe basée sur les classes dans React, parmi lesquels :
- La nécessité de transmettre des
props
entre leconstructor
etsuper
- L'initialisation désordonnée des propriétés dans le
constructor
- C'était une raison invoquée par l'équipe React à l'époque, mais le problème a été résolu avec ES2019
- Les problèmes causés par
this
ne font plus référence au composant
Concepts des hooks React dans Lit
Comme indiqué dans la section Composants et props, Lit ne permet pas de créer des éléments personnalisés à partir d'une fonction. Toutefois, LitElement résout la plupart des principaux problèmes liés aux composants à base de classes React. Exemple :
// 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++;
}
}
Comment est-ce que Lit gère ces problèmes ?
- Le
constructor
n'accepte aucun argument. - Toutes les liaisons
@event
sont automatiquement associées àthis
. - Dans la grande majorité des cas,
this
renvoie à la référence de l'élément personnalisé. - Les propriétés de classe peuvent maintenant être instanciées en tant que membres de classe. Cela efface les implémentations basées sur le constructeur.
Contrôleurs réactifs
Les principaux concepts des hooks existent dans Lit sous la forme de contrôleurs réactifs. Les schémas de contrôleurs réactifs permettent de partager une logique avec état, de diviser les composants en fragments plus petits et plus modulaires, et d'intégrer le cycle de vie de la mise à jour d'un élément.
Un contrôleur réactif est une interface d'objet qui peut intégrer le cycle de vie de la mise à jour d'un hôte contrôleur tel que LitElement.
Le cycle de vie d'un ReactiveController
et d'un reactiveControllerHost
est le suivant :
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>;
}
En construisant un contrôleur réactif et en l'associant à un hôte avec addController
, le cycle de vie du contrôleur est appelé en même temps que celui de l'hôte. Par exemple, reprenez l'exemple d'horloge de la section État et cycle de vie :
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')
);
Dans l'exemple ci-dessus, une horloge simple effectue ce qui suit :
- Elle affiche "Hello World ! Il est…", suivi de l'heure.
- L'horloge est mise à jour toutes les secondes.
- Lorsque le composant est démonté, l'intervalle qui appelle la méthode tick est effacé.
Créer l'échafaudage du composant
Commencez par la déclaration de la classe du composant et ajoutez la fonction 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);
Créer le contrôleur
Passez maintenant à clock.ts
, créez une classe pour le ClockController
et configurez le 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() {
}
// Will not be used but needed for TS compilation
hostUpdate() {};
hostUpdated() {};
}
// Lit (JS) - clock.js
export class ClockController {
constructor(host) {
this.host = host;
host.addController(this);
}
hostConnected() {
}
tick() {
}
hostDisconnected() {
}
}
Un contrôleur réactif peut être construit de n'importe quelle manière tant qu'il partage l'interface du ReactiveController
. Toutefois, l'équipe Lit préfère utiliser, dans la plupart des cas de base, un schéma qui consiste à utiliser une classe avec un constructor
capable d'assimiler l'interface d'un hôte ReactiveControllerHost
, ainsi que toute autre propriété nécessaire à l'initialisation du contrôleur.
Vous devez maintenant convertir les rappels de cycle de vie dans React en rappels du contrôleur. En bref :
componentDidMount
- En rappel
connectedCallback
de LitElement - En rappel
hostConnected
du contrôleur
- En rappel
ComponentWillUnmount
- En rappel
disconnectedCallback
de LitElement - En rappel
hostDisconnected
du contrôleur
- En rappel
Pour en savoir plus sur la conversion du cycle de vie dans React en cycle de vie dans Lit, consultez la section État et cycle de vie.
Implémentez ensuite le rappel hostConnected
et les méthodes tick
, puis effacez l'intervalle dans hostDisconnected
, comme indiqué dans l'exemple de la section État et cycle de vie.
// Lit (TS) - clock.ts
export class ClockController implements ReactiveController {
private readonly host: ReactiveControllerHost;
private interval = 0;
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);
}
hostUpdate() {};
hostUpdated() {};
}
// 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);
}
}
Utiliser le contrôleur
Pour utiliser le contrôleur d'horloge, importez-le, puis mettez à jour le composant dans index.ts
ou 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);
Pour utiliser le contrôleur, vous devez l'instancier en transmettant une référence à l'hôte du contrôleur (qui est le composant <my-element>
), puis utiliser le contrôleur dans la méthode render
.
Déclencher un nouveau rendu dans le contrôleur
Notez que l'heure sera affichée, mais qu'elle ne se met pas à jour. Cela est dû au fait que le contrôleur définit la date toutes les secondes, mais que l'hôte ne se met pas à jour. En fait, la date
change dans la classe ClockController
, mais plus le composant. Autrement dit, une fois que la date
est définie sur le contrôleur, l'hôte doit être invité à exécuter son cycle de vie de mise à jour avec host.requestUpdate()
.
// Lit (TS & JS) - clock.ts / clock.js
private tick() {
this.date = new Date();
this.host.requestUpdate();
}
L'horloge devrait maintenant tourner !
Pour comparer plus en détail les cas d'utilisation courants avec des hooks, consultez la section Niveau avancé - Hooks.
Dans cette section, vous allez apprendre à utiliser les emplacements pour gérer les enfants dans Lit.
Emplacements et enfants
Les emplacements aident à la composition en vous permettant d'imbriquer des composants.
Dans React, les enfants sont hérités via des props. L'emplacement par défaut est props.children
et la fonction render
définit son emplacement. Exemple :
const MyArticle = (props) => {
return <article>{props.children}</article>;
};
N'oubliez pas que les props.children
sont des composants React et non des éléments HTML.
Dans Lit, les enfants sont composés dans la fonction de rendu avec des éléments d'emplacement. Notez que les enfants ne sont pas hérités de la même manière que dans React. Dans Lit, les enfants sont des éléments HTMLElements associés à des emplacements. On appelle cette association une projection.
@customElement("my-article")
export class MyArticle extends LitElement {
render() {
return html`
<article>
<slot></slot>
</article>
`;
}
}
Emplacements multiples
Dans React, l'ajout de plusieurs emplacements revient essentiellement à hériter d'autres props.
const MyArticle = (props) => {
return (
<article>
<header>
{props.headerChildren}
</header>
<section>
{props.sectionChildren}
</section>
</article>
);
};
De même, l'ajout d'autres éléments <slot>
crée davantage d'emplacements dans Lit. Plusieurs emplacements sont définis avec l'attribut name
: <slot name="slot-name">
. Cela permet aux enfants de déclarer l'emplacement qui leur sera attribué.
@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>
`;
}
}
Contenu de l'emplacement par défaut
Un emplacement affiche sa sous-arborescence lorsqu'il n'y a pas de nœuds projetés sur cet emplacement. Si des nœuds y sont projetés, l'emplacement n'affiche pas sa sous-arborescence, mais les nœuds projetés.
@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>
`;
}
}
Affecter des enfants à des emplacements
Dans React, les enfants sont affectés à des emplacements par le biais des propriétés d'un composant. Dans l'exemple ci-dessous, les éléments React sont transmis aux props headerChildren
et sectionChildren
.
const MyNewsArticle = () => {
return (
<MyArticle
headerChildren={<h3>Extry, Extry! Read all about it!</h3>}
sectionChildren={<p>Children are props in React!</p>}
/>
);
};
Dans Lit, les enfants sont affectés à des emplacements à l'aide de l'attribut 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>
`;
}
}
S'il n'y a pas d'emplacement par défaut (<slot>
, par exemple) et qu'aucun emplacement ne possède un attribut name
(<slot name="foo">
, par exemple) qui correspond à l'attribut slot
des enfants de l'élément personnalisé (<div slot="foo">
, par exemple), ce nœud ne sera alors pas projeté et ne s'affichera pas.
Parfois, un développeur peut avoir besoin d'accéder à l'API d'un élément HTMLElement.
Dans cette section, vous allez apprendre à acquérir des références d'éléments dans Lit.
Références dans React
Un composant React est transpilé dans une série d'appels de fonction qui créent un DOM virtuel lorsqu'il est appelé. Ce DOM virtuel est interprété par ReactOM et affiche des éléments HTMLElements.
Dans React, les refs correspondent à de l'espace en mémoire pour contenir un élément HTMLElement généré.
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>
);
};
Dans l'exemple ci-dessus, le composant React effectue ce qui suit :
- Il affiche une entrée de texte vide et un bouton avec du texte.
- Il sélectionne l'entrée lorsque l'utilisateur clique sur le bouton.
Après le rendu initial, React définit inputRef.current
sur l'élément HTMLInputElement
généré via l'attribut ref
.
Références Lit avec @query
Lit se trouve à proximité du navigateur et crée une abstraction très fine par rapport aux fonctionnalités natives du navigateur.
L'équivalent React de refs
dans Lit est l'élément HTMLElement renvoyé par les décorateurs @query
et @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"></input>
<br />
<!-- Bind the click listener -->
<button @click=${this.onButtonClick}>
Click to focus on the input above!
</button>
`;
}
}
Dans l'exemple ci-dessus, le composant Lit effectue ce qui suit :
- Il définit une propriété sur
MyElement
à l'aide du décorateur@query
(création d'un "getter" pour un élémentHTMLInputElement
). - Il déclare et associe un rappel d'événement de clic appelé
onButtonClick
. - Il sélectionne l'entrée lorsque l'utilisateur clique sur le bouton.
Dans JavaScript, les décorateurs @query
et @queryAll
exécutent respectivement querySelector
et querySelectorAll
. Il s'agit de l'équivalent JavaScript de @query('input') inputEl!: HTMLInputElement;
.
get inputEl() {
return this.renderRoot.querySelector('input');
}
Une fois que le composant Lit a validé le modèle de la méthode render
à la racine de my-element
, le décorateur @query
permet alors à inputEl
de renvoyer le premier élément input
trouvé dans la racine de rendu. La valeur null
est renvoyée si @query
ne trouve pas l'élément spécifié.
Si la racine de rendu comporte plusieurs éléments input
, @queryAll
renvoie une liste de nœuds.
Dans cette section, vous allez apprendre à arbitrer l'état entre des composants dans Lit.
Composants réutilisables
React reproduit les pipelines de rendu fonctionnels avec un flux de données descendant. Les parents fournissent l'état aux enfants via les props, et les enfants communiquent avec leurs parents via les rappels trouvés dans les props.
const CounterButton = (props) => {
const label = props.step < 0
? `- ${-1 * props.step}`
: `+ ${props.step}`;
return (
<button
onClick={() =>
props.addToCounter(props.step)}>{label}</button>
);
};
Dans l'exemple ci-dessus, un composant React effectue ce qui suit :
- Il crée un libellé en fonction de la valeur
props.step
. - Il affiche un bouton avec "+step" ou "-step" comme libellé.
- Il met à jour le composant parent en appelant
props.addToCounter
avecprops.step
en tant qu'argument lors d'un clic.
Bien qu'il soit possible de transmettre des rappels dans Lit, les schémas conventionnels sont différents. Dans l'exemple ci-dessus, le composant React peut être écrit en tant que composant Lit dans l'exemple ci-dessous :
@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>
`;
}
}
Dans l'exemple ci-dessus, un composant Lit effectue ce qui suit :
- Il crée la propriété réactive
step
. - Il distribue un événement personnalisé appelé
update-counter
contenant la valeurstep
de l'élément lorsque l'utilisateur clique.
Les événements du navigateur remontent depuis les enfants vers les éléments parents. Ces événements permettent aux enfants de diffuser des événements d'interaction et des changements d'état. React transmet fondamentalement l'état dans le sens opposé. Il est donc rare de voir les composants React distribuer et écouter des événements comme le font les composants Lit.
Composants avec état
Dans React, des hooks sont souvent utilisés pour gérer l'état. Un composant MyCounter
peut être créé en réutilisant le composant CounterButton
. Notez comment addToCounter
est transmis aux deux instances de CounterButton
.
const MyCounter = (props) => {
const [counterSum, setCounterSum] = React.useState(0);
const addToCounter = useCallback(
(step) => {
setCounterSum(counterSum + step);
},
[counterSum, setCounterSum]
);
return (
<div>
<h3>Σ: {counterSum}</h3>
<CounterButton
step={-1}
addToCounter={addToCounter} />
<CounterButton
step={1}
addToCounter={addToCounter} />
</div>
);
};
L'exemple ci-dessus illustre ce qui suit :
- Un état
count
est créé. - Un rappel qui ajoute un nombre à un état
count
est créé. CounterButton
utiliseaddToCounter
pour mettre à jourcount
parstep
à chaque clic.
Une implémentation similaire de MyCounter
peut être réalisée dans Lit. Notez que addToCounter
n'est pas transmis à counter-button
. Au lieu de cela, le rappel est lié en tant qu'écouteur d'événements à l'événement @update-counter
sur un élément parent.
@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>Σ ${this.count}</h3>
<counter-button step="-1"></counter-button>
<counter-button step="1"></counter-button>
</div>
`;
}
}
L'exemple ci-dessus illustre ce qui suit :
- Une propriété réactive appelée
count
, qui met à jour le composant lorsque la valeur est modifiée, est créée. - Le rappel
addToCounter
est lié à l'écouteur d'événements@update-counter
. count
est mis à jour en ajoutant la valeur trouvée dans ledetail.step
de l'événementupdate-counter
.- La valeur
step
decounter-button
est définie via l'attributstep
.
Il est plus courant d'utiliser les propriétés réactives de Lit pour diffuser les changements des parents aux enfants. De même, il est recommandé d'utiliser le système d'événements du navigateur pour afficher les détails de bas en haut.
Cette approche respecte les bonnes pratiques, ainsi que l'objectif de Lit visant à assurer la compatibilité des composants Web sur de multiples plates-formes.
Dans cette section, vous allez étudier les styles dans Lit.
Styles
Lit propose plusieurs méthodes pour appliquer un style aux éléments, ainsi qu'une solution intégrée.
Styles intégrés
Lit accepte les styles intégrés ainsi que leurs liaisons.
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>
`;
}
}
Dans l'exemple ci-dessus, un style intégré est appliqué à deux titres.
Essayez maintenant de lier une bordure border: 1px solid black
au texte orange :
<h1 style="color:orange;${'border: 1px solid black;'}">This text is orange</h1>
Le calcul de la chaîne de style pouvant être fastidieux, Lit fournit une directive pour vous aider.
styleMap
La directive styleMap
permet de définir plus facilement des styles intégrés à l'aide de JavaScript. Exemple :
import {LitElement, html, css} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {styleMap} from 'lit/directives/style-map';
@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'exemple ci-dessus illustre ce qui suit :
- Un
h1
avec une bordure et un sélecteur de couleur est affiché. border-color
est remplacé par la valeur du sélecteur de couleur.
styleMap
est également utilisé pour définir les styles du h1
. styleMap
suit une syntaxe semblable à la syntaxe de liaison de l'attribut style
de React.
CSSResult
Pour définir le style des composants, il est recommandé d'utiliser le littéral de modèle balisé 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'exemple ci-dessus illustre ce qui suit :
- Un littéral de modèle balisé CSS est déclaré avec une liaison.
- Les couleurs de deux éléments
h1
avec ID sont définies.
Avantages de la balise de modèle css
:
- Analysée une fois par classe versus par instance
- Implémentée avec à l'esprit la possibilité de réutiliser facilement les modules
- Peut séparer facilement les styles dans leurs propres fichiers
- Compatible avec le polyfill des propriétés personnalisées CSS
Notez également la balise <style>
dans index.html
:
<!-- index.html -->
<style>
h1 {
color: red !important;
}
</style>
Lit va définir les styles de vos composants en fonction de leur racine. Autrement dit, les styles ne seront pas placés à l'intérieur ni à l'extérieur. Pour transmettre des styles entre les composants, l'équipe Lit recommande d'utiliser les propriétés CSS personnalisées, car elles peuvent pénétrer la portée des styles Lit.
Balises de style
Il est également possible d'insérer simplement des balises <style>
dans vos modèles. Le navigateur déduplique ces balises de style, mais en les plaçant dans vos modèles, elles sont analysées par instance de composant plutôt que par classe, comme c'est le cas avec le modèle balisé css
. De plus, la déduplication des CSSResult
par le navigateur est bien plus rapide.
Balises de lien
Il est également possible d'utiliser une <link rel="stylesheet">
dans votre modèle pour les styles. Toutefois, ce n'est pas recommandée, car cela peut entraîner un flash initial de contenu sans style.
JSX et création de modèles
Lit et DOM virtuel
Lit-html n'inclut pas de DOM virtuel traditionnel qui différencie chaque nœud individuel. Au lieu de cela, il utilise des fonctionnalités de performances intrinsèques conformément à la spécification des littéraux de modèles balisés d'ES2015. Les littéraux de modèles balisés sont des chaînes de littéraux de modèles auxquelles sont associées des fonctions de balise.
Voici un exemple de littéral de modèle :
const str = 'string';
console.log(`This is a template literal ${str}`);
Voici un exemple de littéral de modèle balisé :
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
Dans l'exemple ci-dessus, la balise est la fonction tag
, et la fonction f
renvoie un appel d'un littéral de modèle balisé.
Les remarquables performances dans Lit viennent en grande partie du fait que les tableaux de chaînes transmis à la fonction de balise ont le même pointeur (comme illustré dans le second console.log
). Le navigateur ne recrée pas un nouveau tableau de strings
à chaque appel de fonction de balise, car il utilise le même littéral de modèle (c.-à-d. à la même position dans l'AST). Ainsi, la liaison, l'analyse et la mise en cache de modèles dans Lit peuvent tirer parti de ces fonctionnalités sans que cela entraîne une surcharge de l'environnement d'exécution.
Ce comportement intégré au navigateur pour les littéraux de modèles balisés donne à Lit un avantage considérable en termes de performances. La plupart des DOM virtuels exécutent la majorité de leurs tâches en JavaScript. Toutefois, les littéraux de modèles balisés exécutent la commande diff en grande partie dans le C++ du navigateur.
Si vous voulez commencer à utiliser les littéraux de modèle balisés HTML avec React ou Preact, l'équipe Lit recommande la bibliothèque htm
.
Toutefois, comme pour le site Google Codelabs et plusieurs éditeurs de code en ligne, vous remarquerez que la mise en évidence de la syntaxe des littéraux de modèles balisés n'est pas très courante. Certains IDE et éditeurs de texte acceptent ces outils par défaut (par exemple, le surligneur de bloc de codes Atom et GitHub. L'équipe Lit collabore également très étroitement avec la communauté pour gérer des projets tels que lit-plugin
(un plug-in VS Code qui permet de mettre en évidence la syntaxe, de valider le type et de bénéficier d'IntelliSense dans vos projets Lit).
Lit et JSX + DOM React
JSX ne s'exécute pas dans le navigateur et utilise à la place un préprocesseur pour convertir les appels de fonction JSX en JavaScript (généralement via Babel).
Par exemple, Babel transforme ce qui suit :
const element = <div className="title">Hello World!</div>;
ReactDOM.render(element, mountNode);
comme ceci :
const element = React.createElement('div', {className: 'title'}, 'Hello World!');
ReactDOM.render(element, mountNode);
Le DOM React convertit ensuite le résultat React en DOM réel (propriétés, attributs, écouteurs d'événements, etc.).
Lit-html utilise des littéraux de modèles balisés qui peuvent s'exécuter dans le navigateur sans transpilation ni préprocesseur. Autrement dit, pour commencer avec Lit, vous avez besoin uniquement d'un fichier HTML, d'un script de module ES et d'un serveur. Voici un script entièrement exécutable avec le navigateur :
<!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>
En outre, comme le système de création de modèles de Lit (lit-html) utilise directement l'API DOM et non un DOM virtuel classique, Lit 2 fait moins de 5 ko une fois compressé au format .gz contre plus de 40 ko pour React (2,8 ko) + le DOM React (39,4 ko).
Événements
React utilise un système d'événements synthétiques. Cela signifie que le DOM React doit définir chaque événement qui sera utilisé sur chaque composant et fournir un écouteur d'événements en camelCase pour chaque type de nœud. Par conséquent, JSX n'a pas de méthode permettant de définir un écouteur d'événements pour un événement personnalisé. Les développeurs doivent utiliser une ref
, puis appliquer impérativement un écouteur. L'expérience développeur est ainsi médiocre quand des bibliothèques pour lesquelles React n'est pas utilisé sont intégrées, ce qui implique d'écrire un wrapper propre à React.
Lit-html accède directement au DOM et utilise des événements natifs. L'ajout d'écouteurs d'événements est aussi simple que @event-name=${eventNameListener}
. Autrement dit, moins d'analyses d'exécution sont réalisées pour ajouter des écouteurs d'événements et déclencher des événements.
Composants et props
Composants et éléments personnalisés dans React
"En coulisses", LitElement utilise des éléments personnalisés pour empaqueter ses composants. Ces éléments personnalisés introduisent des compromis entre les composants React lorsqu'il s'agit de séparer des composants (l'état et le cycle de vie sont décrits plus en détail dans la section État et cycle de vie).
Voici quelques avantages des éléments personnalisés en tant que système de composants :
- Ce sont des éléments natifs du navigateur qui ne nécessitent aucun outil.
- Ils s'intègrent dans chaque API de navigateur (de
innerHTML
etdocument.createElement
àquerySelector
). - Ils sont généralement utilisables dans tous les frameworks.
- Ils peuvent être enregistrés de manière différée avec
customElements.define
et le DOM "hydrate".
Voici quelques inconvénients des éléments personnalisés par rapport aux composants React :
- Il est impossible de créer un élément personnalisé sans définir une classe (pas de composants fonctionnels de type JSX).
- Ces éléments doivent contenir une balise fermante.
- Remarque : Malgré l'aspect pratique pour les développeurs, les fournisseurs de navigateurs ont tendance à regretter la spécification des balises à fermeture automatique. C'est pourquoi les spécifications les plus récentes n'incluent généralement pas de balises à fermeture automatique.
- Un nœud supplémentaire est ajouté à l'arborescence DOM, ce qui peut causer des problèmes de mise en page.
- Ils doivent être enregistrés via JavaScript.
Lit a choisi les éléments personnalisés plutôt qu'un système d'éléments sur mesure, car ils sont intégrés au navigateur. L'équipe Lit estime que les avantages sur le plan de la compatibilité avec plusieurs frameworks l'emportent sur ceux qu'offre une couche d'abstraction de composants. En réalité, les efforts de l'équipe de Lit dans l'espace lit-ssr ont permis de surmonter les principaux problèmes d'enregistrement JavaScript. En outre, certaines entreprises telles que GitHub profitent de l'enregistrement différé des éléments personnalisés pour améliorer progressivement les pages en y intégrant une touche facultative.
Transmettre des données à des éléments personnalisés
On estime souvent à tort au sujet des éléments personnalisés que les données ne peuvent être transmises que sous forme de chaînes. Cette idée fausse vient probablement du fait que les attributs des éléments ne peuvent être écrits que sous forme de chaînes. Même s'il est vrai que Lit convertit les attributs de chaîne en leurs types définis, les éléments personnalisés peuvent aussi accepter des données complexes en tant que propriétés.
Par exemple, prenons la définition de LitElement suivante :
// 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>`;
}
}
Une propriété réactive primitive num
est définie, laquelle convertira la valeur de chaîne d'un attribut en number
. Une structure de données complexe est ensuite introduite avec attribute:false
, qui désactive le traitement des attributs dans Lit.
Voici comment transmettre des données à cet élément personnalisé :
<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>
État et cycle de vie
Autres rappels de cycle de vie dans React
static getDerivedStateFromProps
Il n'y a pas d'équivalent dans Lit, car les props et l'état sont des propriétés de la même classe.
shouldComponentUpdate
shouldUpdate
est son équivalent dans Lit.- Il est appelé lors du premier rendu (contrairement à React).
- Il a une fonction semblable à
shouldComponentUpdate
dans React.
getSnapshotBeforeUpdate
Dans Lit, getSnapshotBeforeUpdate
est semblable à update
et à willUpdate
.
willUpdate
- Ce rappel est appelé avant
update
. - Contrairement à
getSnapshotBeforeUpdate
,willUpdate
est appelé avantrender
. - La modification des propriétés réactives dans
willUpdate
ne déclenche pas de nouveau le cycle de mise à jour. - Idéal pour calculer les valeurs de propriété qui dépendent d'autres propriétés et qui sont utilisées dans le reste du processus de mise à jour.
- Cette méthode est appelée sur le serveur dans le rendu côté serveur. Il n'est donc pas recommandé d'accéder au DOM dans le cas présent.
update
- Ce rappel est appelé après
willUpdate
. - Contrairement à
getSnapshotBeforeUpdate
,update
est appelé avantrender
. - La modification des propriétés réactives dans
update
ne déclenche pas de nouveau le cycle de mise à jour si ce rappel est modifié avant d'appelersuper.update
. - Idéal pour capturer les informations du DOM entourant le composant avant que le résultat affiché ne soit validée dans le DOM.
- Cette méthode n'est pas appelée sur le serveur dans le rendu côté serveur.
Autres rappels de cycle de vie dans Lit
Plusieurs rappels de cycle de vie n'ont pas été mentionnés dans la section précédente, car ils n'ont pas d'équivalents dans React. à savoir :
attributeChangedCallback
Ce rappel est appelé quand l'un des attributs observedAttributes
de l'élément est modifié. observedAttributes
et attributeChangedCallback
font tous deux partie de la spécification des éléments personnalisés et sont implémentés par Lit en arrière-plan pour fournir une API d'attribut pour les éléments Lit.
adoptedCallback
Ce rappel est appelé lorsque le composant est déplacé vers un nouveau document (par exemple, d'un documentFragment
de l'élément HTMLTemplateElement
vers le document
principal). Il fait également partie de la spécification des éléments personnalisés et ne doit être utilisé que pour des cas d'utilisation avancés lorsque le composant modifie des documents.
Autres méthodes et propriétés du cycle de vie
Ces méthodes et propriétés sont des membres de la classe que vous pouvez appeler, ignorer ou attendre pour faciliter la manipulation du processus de cycle de vie.
updateComplete
Il s'agit d'une Promise
qui se résout une fois la mise à jour de l'élément terminée, étant donné que les cycles de mise à jour et de rendu sont asynchrones. Exemple :
async nextButtonClicked() {
this.step++;
// Wait for the next "step" state to render
await this.updateComplete;
this.dispatchEvent(new Event('step-rendered'));
}
getUpdateComplete
Cette méthode doit être remplacée pour être personnalisée lorsque updateComplete
se résout. Ceci est fréquent lorsqu'un composant effectue le rendu d'un composant enfant, et que leurs cycles de rendu doivent être synchronisés. Exemple :
class MyElement extends LitElement {
...
async getUpdateComplete() {
await super.getUpdateComplete();
await this.myChild.updateComplete;
}
}
performUpdate
Cette méthode correspond à ce qui appelle les rappels de cycle de vie de mise à jour. Cela n'est généralement pas nécessaire, sauf dans de rares cas où la mise à jour doit être effectuée de manière synchrone ou pour une planification personnalisée.
hasUpdated
Cette propriété est true
si le composant a été mis à jour au moins une fois.
isConnected
Cette propriété fait partie de la spécification des éléments personnalisés. Elle est true
si l'élément est actuellement associé à l'arborescence du document principal.
Visualisation du cycle de vie d'une mise à jour dans Lit
Le cycle de vie d'une mise à jour comprend trois parties :
- Avant la mise à jour
- Mise à jour
- Après la mise à jour
Avant la mise à jour
Après la requestUpdate
, une mise à jour planifiée est disponible.
Mise à jour
Après la mise à jour
Hooks
Pourquoi utiliser des hooks
Les hooks ont été introduits dans React pour les cas d'utilisation simples de fonctions composants nécessitant un état. Dans de nombreux cas simples, les fonctions composants avec des hooks sont souvent beaucoup plus simples et lisibles que les composants à base de classes. Toutefois, lors de l'introduction de mises à jour d'état asynchrones et de la transmission de données entre les hooks ou les effets, le schéma de hooks ne suffit pas toujours, tandis qu'une solution basée sur les classes, comme les contrôleurs réactifs, est souvent plus efficace.
Hooks et contrôleurs qui envoient des requêtes à une API
Un hook est souvent écrit pour demander des données à une API. Prenons l'exemple de cette fonction composant React qui effectue ce qui suit :
index.tsx
- Affiche du texte
- Affiche la réponse de
useAPI
- ID et nom d'utilisateur
- Message d'erreur
- 404 une fois l'utilisateur 11 atteint (par nature)
- Erreur d'annulation si l'extraction de l'API est annulée
- Chargement du message
- Affiche un bouton d'action
- Utilisateur suivant : qui extrait l'API pour l'utilisateur suivant
- Annuler : abandonne l'extraction de l'API et affiche une erreur
useApi.tsx
- Définit un hook personnalisé
useApi
- Va extraire de manière asynchrone un objet utilisateur d'une API
- Émet :
- Nom d'utilisateur
- Si l'extraction est en cours de chargement
- Tous les messages d'erreur
- Un rappel pour annuler l'extraction
- Annule les extractions en cours si le composant est démonté
- Définit un hook personnalisé
Découvrez l'implémentation de Lit et du contrôleur réactif.
Conclusions :
- Les contrôleurs réactifs sont semblables aux hooks personnalisés.
- Transmission des données ne pouvant pas être affichées entre les rappels et les effets.
- React utilise
useRef
pour transmettre des données entreuseEffect
etuseCallback
. - Lit utilise une propriété de classe privée.
- React reproduit essentiellement le comportement d'une propriété de classe privée.
- React utilise
Enfants
Emplacement par défaut
Lorsque des éléments HTML n'ont pas d'attribut slot
, ils sont affectés à l'emplacement sans nom par défaut. Dans l'exemple ci-dessous, MyApp
place un paragraphe à un emplacement nommé. L'autre paragraphe sera par défaut à l'emplacement sans nom.
@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>
`;
}
}
Modification des emplacements
Lorsque la structure des descendants de l'emplacement change, un événement slotchange
est déclenché. Un composant Lit peut lier un écouteur d'événements à un événement slotchange
. Dans l'exemple ci-dessous, les assignedNodes du premier emplacement trouvé à la shadowRoot
seront consignés dans la console en cas de 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>
`;
}
}
Refs
Génération de références
Lit et React montrent tous les deux une référence à un élément HTMLElement une fois que leurs fonctions render
ont été appelées. Toutefois, il est intéressant d'examiner comment React et Lit composent le DOM qui est renvoyé par la suite via un décorateur @query
dans Lit ou une référence dans React.
React est un pipeline fonctionnel qui crée des composants React et non des éléments HTMLElements. Étant donné qu'un attribut ref est déclaré avant l'affichage d'un élément HTMLElement, un espace en mémoire est alloué. C'est la raison pour laquelle, vous voyez null
comme valeur initiale d'un attribut Ref, car l'élément DOM réel n'a pas encore été créé ou rendu (par exemple, useRef(null)
).
Une fois que ReactDOM convertit un composant React en élément HTMLElement, il recherche un attribut appelé ref
dans le composant ReactComponent. Le cas échéant, ReactDOM ajoute la référence de l'élément HTMLElement à ref.current
.
LitElement utilise la fonction de balise de modèle html
de lit-html pour composer un élément de modèle en arrière-plan. LitElement remplace le contenu du modèle dans le Shadow DOM d'un élément personnalisé après le rendu. Le Shadow DOM est une arborescence DOM étendue, qui est encapsulée par une racine fantôme. Le décorateur @query
crée ensuite une méthode getter pour la propriété, qui exécute essentiellement un this.shadowRoot.querySelector
à la racine étendue.
Interroger plusieurs éléments
Dans l'exemple ci-dessous, le décorateur @queryAll
renvoie les deux paragraphes de la racine fantôme en tant que 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>
`;
}
}
Globalement, @queryAll
crée un getter pour les paragraphs
, qui renvoie les résultats de this.shadowRoot.querySelectorAll()
. Dans JavaScript, un getter peut être déclaré pour effectuer la même fonction :
get paragraphs() {
return this.renderRoot.querySelectorAll('p');
}
Interroger les éléments qui changent
Le décorateur @queryAsync
est mieux adapté pour gérer un nœud qui peut changer en fonction de l'état d'une autre propriété d'élément.
Dans l'exemple ci-dessous, @queryAsync
trouvera le premier élément de paragraphe. Toutefois, un élément de paragraphe ne sera affiché que si renderParagraph
génère un nombre impair de manière aléatoire. La directive @queryAsync
renverra une promesse qui se résout lorsque le premier paragraphe sera disponible.
@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()}
`;
}
}
État de médiation
Dans React, la convention est d'utiliser des rappels, car l'état est paramétré par React lui-même. React fait de son mieux pour ne pas dépendre de l'état fourni par les éléments. Le DOM est simplement un effet du processus de rendu.
État externe
Il est possible d'utiliser Redux, MobX ou toute autre bibliothèque de gestion d'état parallèlement à Lit.
Les composants Lit sont créés dans le champ d'application du navigateur. Ainsi, toutes les bibliothèques qui existent également dans ce champ d'application sont disponibles pour Lit. De nombreuses bibliothèques incroyables ont été créées pour utiliser les systèmes de gestion d'état existants dans Lit.
Voici une série de Vaadin, qui explique comment exploiter Redux dans un composant Lit.
Consultez lit-mobx d'Adobe pour découvrir comment un grand site peut exploiter MobX dans Lit.
Découvrez également les éléments Apollo pour voir comment les développeurs intègrent GraphQL dans leurs composants Web.
Lit fonctionne avec les fonctionnalités natives du navigateur. La plupart des solutions de gestion d'état dans le champ d'application du navigateur peuvent être utilisées dans un composant Lit.
Styles
Shadow DOM
Pour encapsuler de façon native des styles et le DOM dans un élément personnalisé, Lit utilise Shadow DOM. Les racines fantômes génèrent une arborescence fantôme distincte de l'arborescence du document principal. La plupart des styles sont donc limités à ce document. Certains styles passent outre cette limitation (par exemple, la couleur et d'autres styles liés à la police).
Shadow DOM introduit également de nouveaux concepts et sélecteurs dans la spécification 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.
*/
}
Partager les styles
Lit facilite le partage des styles entre les composants sous la forme de CSSTemplateResults
via les balises de modèle css
. Exemple :
// 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>`
}
}
Thématisation
Les racines fantômes, qui sont généralement des approches descendantes de balises de style, présentent un véritable défi pour la thématisation classique. La méthode classique d'aborder la thématisation avec les Web Components qui utilisent Shadow DOM consiste à exposer une API de style via des propriétés personnalisées CSS. Par exemple, Material Design utilise le schéma suivant :
.mdc-textfield-outline {
border-color: var(--mdc-theme-primary, /* default value */ #...);
}
.mdc-textfield--input {
caret-color: var(--mdc-theme-primary, #...);
}
L'utilisateur changerait alors le thème du site en appliquant des valeurs de propriétés personnalisées :
html {
--mdc-theme-primary: #F00;
}
html[dark] {
--mdc-theme-primary: #F88;
}
Si la thématisation descendante est incontournable et que vous ne pouvez pas exposer de styles, il est toujours possible de désactiver le Shadow DOM en ignorant createRenderRoot
pour renvoyer this
. Le modèle de vos composants sera alors affiché à l'élément personnalisé lui-même plutôt qu'à une racine fantôme associée à l'élément personnalisé. Vous perdrez alors l'encapsulation du style et du DOM, ainsi que les emplacements.
Production
IE 11
Si vous avez besoin de prendre en charge des navigateurs plus anciens comme IE 11, vous devrez charger des polyfills qui ajouteront environ 33 ko. Pour en savoir plus, cliquez ici.
Groupes conditionnels
L'équipe Lit recommande de diffuser deux groupes différents, un pour IE 11 et l'autre pour les navigateurs récents. Cela présente plusieurs avantages :
- Le service ES 6 est plus rapide et répond à la plupart de vos clients.
- L'ES 5 transpilé augmente considérablement la taille du groupe.
- Les groupes conditionnels vous offrent le meilleur des deux possibilités.
- Compatibilité avec Internet Explorer 11
- Aucun ralentissement sur les navigateurs récents
Pour en savoir plus sur la création d'un groupe diffusé de manière conditionnelle, rendez-vous sur notre site de documentation.