1. Introducción
Última actualización: 10/08/2021
Componentes web
Los componentes web son un conjunto de APIs de plataforma web que te permiten crear nuevas etiquetas HTML personalizadas, reutilizables y encapsuladas para usarlas en páginas y apps web. Los componentes y widgets personalizados creados en los estándares de componentes web funcionarán en todos los navegadores modernos y pueden usarse con cualquier marco o biblioteca de JavaScript que funcione con HTML.
¿Qué es Lit?
Lit es una biblioteca simple para compilar componentes web rápidos y ligeros que funcionan en cualquier marco de trabajo o sin ningún framework. Con Lit, puedes compilar componentes, aplicaciones, sistemas de diseño que se pueden compartir y mucho más.
Lit proporciona APIs para simplificar tareas comunes de componentes web, como la administración de propiedades, atributos y renderización.
Qué aprenderás
- ¿Qué es un componente web?
- Conceptos de componentes web
- Cómo compilar un componente web
- ¿Qué son lit-html y LitElement?
- Qué hace Lit sobre un componente web
Qué compilarás
- Un componente web vanilla con Me gusta o No me gusta
- Un componente web basado en Lit basado en Me gusta o No me gusta
Requisitos
- Cualquier navegador actualizado (Chrome, Safari, Firefox, Chromium Edge) Los componentes web funcionan en todos los navegadores modernos, y los polyfills están disponibles para Microsoft Internet Explorer 11 y Microsoft Edge que no admite Chromium.
- Conocimiento de HTML, CSS, JavaScript y Herramientas para desarrolladores de Chrome
2. Cómo prepararte y exploración de Playground
Accede al código
A lo largo del codelab, habrá vínculos a la zona de pruebas de Lit, como este:
El Playground es una zona de pruebas de código que se ejecuta completamente en el navegador. Puede compilar y ejecutar archivos de TypeScript y JavaScript, y también puede resolver automáticamente las importaciones a módulos de nodos. p.ej.,
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://unpkg.com/lit?module';
Puedes realizar todo el instructivo en la zona de pruebas de Lit y usar estos puntos de control como puntos de partida. Si usas VS Code, puedes usar estos puntos de control para descargar el código inicial de cualquier paso y usarlos para revisar tu trabajo.
Explora la IU de la zona de pruebas de Lit
La captura de pantalla de la IU de la zona de pruebas de Lit destaca las secciones que usarás en este codelab.
- Selector de archivos. Observa el botón de signo más...
- Editor de archivos.
- Vista previa del código
- Botón para volver a cargar
- Botón para descargar.
Configuración de VS Code (avanzada)
Estos son los beneficios de usar esta configuración de VS Code:
- Verificación del tipo de plantilla
- Plantilla: intellisense autocompletado
Si tienes NPM y VS Code (con el complemento lit-plugin) ya instalados y sabes cómo usar ese entorno, puedes descargar e iniciar estos proyectos de la siguiente manera:
- Presiona el botón de descarga
- Extrae el contenido del archivo tar en un directorio.
- Instala un servidor de desarrollo que pueda resolver especificadores de módulos básicos (el equipo de Lit recomienda @web/dev-server).
- Este es un ejemplo de
package.json
- Este es un ejemplo de
- Ejecuta el servidor de desarrollo y abre tu navegador (si usas
@web/dev-server
, puedes usarnpx web-dev-server --node-resolve --watch --open
).- Si usas el ejemplo
package.json
, utilizanpm run serve
.
- Si usas el ejemplo
3. Cómo definir un elemento personalizado
Elementos personalizados
Los componentes web son una colección de 4 APIs web nativas. Son los siguientes:
- Módulos de ES
- Elementos personalizados
- Shadow DOM
- Plantillas HTML
Ya usaste la especificación de módulos ES, que te permite crear módulos JavaScript con importaciones y exportaciones que se cargan en la página con <script type="module">
.
Cómo definir un elemento personalizado
La especificación Custom Elements les permite a los usuarios definir sus propios elementos HTML con JavaScript. Los nombres deben contener un guion (-
) para diferenciarlos de los elementos nativos del navegador. Borra el archivo index.js
y define una clase de elemento personalizado:
index.js
class RatingElement extends HTMLElement {}
customElements.define('rating-element', RatingElement);
Un elemento personalizado se define asociando una clase que extiende HTMLElement
con un nombre de etiqueta con guion. La llamada a customElements.define
le indica al navegador que asocie la clase RatingElement
con el tagName ‘rating-element'
. Esto significa que cada elemento de tu documento con el nombre <rating-element>
se asociará con esta clase.
Coloca un elemento <rating-element>
en el cuerpo del documento y observa qué se renderiza.
index.html
<body>
<rating-element></rating-element>
</body>
Si observas el resultado, verás que no se renderizó nada. Este es el comportamiento esperado, ya que no le indicaste al navegador cómo renderizar <rating-element>
. Para confirmar que la definición del elemento personalizado se estableció correctamente, selecciona <rating-element>
en las Herramientas para desarrolladores de Chrome. de elementos y, en la consola, llamar a lo siguiente:
$0.constructor
El resultado debería ser el siguiente:
class RatingElement extends HTMLElement {}
Ciclo de vida del elemento personalizado
Los elementos personalizados incluyen un conjunto de hooks de ciclo de vida. Son los siguientes:
constructor
connectedCallback
disconnectedCallback
attributeChangedCallback
adoptedCallback
Se llama a constructor
cuando se crea el elemento por primera vez, por ejemplo, llamando a document.createElement(‘rating-element')
o new RatingElement()
. El constructor es un buen lugar para configurar tu elemento, pero generalmente no se considera una práctica recomendada realizar manipulaciones del DOM en el constructor para el "inicio" del elemento. por motivos de rendimiento.
Se llama a connectedCallback
cuando el elemento personalizado se adjunta al DOM. Normalmente, aquí es donde ocurren las manipulaciones iniciales del DOM.
Se llama a disconnectedCallback
después de que se quita el elemento personalizado del DOM.
Se llama a attributeChangedCallback(attrName, oldValue, newValue)
cuando cambia cualquiera de los atributos especificados por el usuario.
Se llama a adoptedCallback
cuando el elemento personalizado se adopta de otro documentFragment
en el documento principal a través de adoptNode
, como en HTMLTemplateElement
.
DOM de renderización
Ahora, regresa al elemento personalizado y asócialo con algún DOM. Configura el contenido del elemento cuando se adjunte al DOM:
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
this.innerHTML = `
<style>
rating-element {
display: inline-flex;
align-items: center;
}
rating-element button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
`;
}
}
customElements.define('rating-element', RatingElement);
En constructor
, almacenas una propiedad de instancia llamada rating
en el elemento. En connectedCallback
, agregas elementos secundarios del DOM a <rating-element>
para mostrar la calificación actual, junto con los botones Me gusta y No me gusta.
4. Shadow DOM
¿Por qué elegir Shadow DOM?
En el paso anterior, notarás que los selectores de la etiqueta de estilo que insertaste seleccionan cualquier elemento de calificación en la página, así como cualquier botón. Esto puede provocar que los estilos se filtren del elemento y se seleccionen otros nodos a los que tal vez no quieras aplicar ajustes de diseño. Además, otros diseños fuera de este elemento personalizado pueden definir el estilo de los nodos dentro de tu elemento personalizado de manera no intencional. Por ejemplo, prueba colocar una etiqueta de estilo en el encabezado del documento principal:
index.html
<!DOCTYPE html>
<html>
<head>
<script src="./index.js" type="module"></script>
<style>
span {
border: 1px solid red;
}
</style>
</head>
<body>
<rating-element></rating-element>
</body>
</html>
Tu resultado debería tener un cuadro de borde rojo alrededor del intervalo de la calificación. Este es un caso trivial, pero la falta de encapsulamiento del DOM puede generar problemas más grandes para aplicaciones más complejas. Aquí es donde entra en juego Shadow DOM.
Cómo adjuntar una shadow root
Adjunta una Shadow Root al elemento y renderiza el DOM dentro de esa raíz:
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
`;
}
}
customElements.define('rating-element', RatingElement);
Cuando actualices la página, notarás que los estilos del documento principal ya no pueden seleccionar los nodos dentro de la raíz secundaria.
¿Cómo lo hiciste? En el connectedCallback
, llamaste a this.attachShadow
, que adjunta una shadow root a un elemento. El modo open
significa que el contenido paralelo se puede inspeccionar y también permite que se pueda acceder a la shadow root mediante this.shadowRoot
. Observa también el componente web en el inspector de Chrome:
Ahora, deberías ver una shadow root expandible que contiene el contenido. Todo lo que está dentro de esa shadow root se llama Shadow DOM. Si seleccionas el elemento de calificación en las Herramientas para desarrolladores de Chrome y llamas a $0.children
, notarás que no muestra ningún elemento secundario. Esto se debe a que el Shadow DOM no se considera parte del mismo árbol del DOM que los elementos secundarios directos, sino como el Shadow Tree.
DOM claro
Un experimento: Agrega un nodo como elemento secundario directo de <rating-element>
:
index.html
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>
Actualiza la página y verás que este nuevo nodo del DOM en el Light DOM del elemento personalizado no aparece en ella. Esto se debe a que el Shadow DOM tiene funciones para controlar cómo se proyectan los nodos del Light DOM en el shadow DOM mediante elementos <slot>
.
5. Plantillas HTML
Por qué usar plantillas
El uso de innerHTML
y strings literales de plantilla sin limpieza puede causar problemas de seguridad con la inyección de secuencias de comandos. En el pasado, los métodos incluían el uso de DocumentFragment
, pero también conlleva otros problemas, como la carga de imágenes y la ejecución de secuencias de comandos cuando se definen las plantillas, además de la introducción de obstáculos para la reutilización. Aquí es donde entra en juego el elemento <template>
. Las plantillas proporcionan DOM inerte, un método de alto rendimiento para clonar nodos y plantillas reutilizables.
Cómo usar plantillas
A continuación, realiza la transición del componente para usar plantillas HTML:
index.html
<body>
<template id="rating-element-template">
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating"></span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
</template>
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>
</body>
Aquí moviste el contenido del DOM a una etiqueta de plantilla en el DOM del documento principal. Ahora refactoriza la definición del elemento personalizado:
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
const templateContent = document.getElementById('rating-element-template').content;
const clonedContent = templateContent.cloneNode(true);
shadowRoot.appendChild(clonedContent);
this.shadowRoot.querySelector('.rating').innerText = this.rating;
}
}
customElements.define('rating-element', RatingElement);
Para usar este elemento de plantilla, debes consultarla, obtener su contenido y clonar los nodos con templateContent.cloneNode
en los que el argumento true
realiza una clonación profunda. Luego, inicializas el dominio con los datos.
Felicitaciones, ahora tienes un componente web. Lamentablemente, aún no hace nada, así que a continuación agrega algunas funcionalidades.
6. Agrega funciones
Vinculaciones de propiedades
Actualmente, la única forma de establecer la calificación en el elemento de calificación es construir el elemento, establecer la propiedad rating
en el objeto y, luego, colocarlo en la página. Desafortunadamente, no es así como los elementos HTML nativos suelen funcionar. Los elementos HTML nativos suelen actualizarse con cambios de propiedad y atributo.
Para que el elemento personalizado actualice la vista cuando cambie la propiedad rating
, agrega las siguientes líneas:
index.js
constructor() {
super();
this._rating = 0;
}
set rating(value) {
this._rating = value;
if (!this.shadowRoot) {
return;
}
const ratingEl = this.shadowRoot.querySelector('.rating');
if (ratingEl) {
ratingEl.innerText = this._rating;
}
}
get rating() {
return this._rating;
}
Agregas un método set y un método get para la propiedad de calificación y, luego, actualizas el texto del elemento de calificación si está disponible. Esto significa que si estableces la propiedad de calificación en el elemento, la vista se actualizará. realiza una prueba rápida en tu consola de Herramientas para desarrolladores.
Vinculaciones de atributos
Ahora, actualiza la vista cuando cambie el atributo. Esto es similar a una entrada que actualiza su vista cuando configuras <input value="newValue">
. Por suerte, el ciclo de vida del componente web incluye attributeChangedCallback
. Agrega las siguientes líneas para actualizar la calificación:
index.js
static get observedAttributes() {
return ['rating'];
}
attributeChangedCallback(attributeName, oldValue, newValue) {
if (attributeName === 'rating') {
const newRating = Number(newValue);
this.rating = newRating;
}
}
Para que se active attributeChangedCallback
, debes establecer un método get estático para RatingElement.observedAttributes which defines the attributes to be observed for changes
. Luego, debes establecer la calificación de forma declarativa en el DOM. Pruebe lo siguiente:
index.html
<rating-element rating="5"></rating-element>
Ahora la calificación debería actualizarse de forma declarativa.
Funcionalidad del botón
Ahora, lo único que falta es la funcionalidad del botón. El comportamiento de este componente debería permitirle al usuario proporcionar una calificación de un solo voto positivo o negativo, así como proporcionar comentarios visuales. Puedes implementar esto con algunos objetos de escucha de eventos y una propiedad de reflejo, pero primero actualiza los estilos para proporcionar comentarios visuales agregando las siguientes líneas:
index.html
<style>
...
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
</style>
En Shadow DOM, el selector :host
hace referencia al nodo o elemento personalizado al que se adjunta la Shadow Root. En este caso, si el atributo vote
es "up"
, el botón de Me gusta se volverá verde, pero si vote
es "down", then it will turn the thumb-down button red
. Ahora implementa la lógica para esto creando una propiedad o un atributo reflectante para vote
de manera similar a como implementaste rating
. Comienza con el método set y el método get de la propiedad:
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
}
set vote(newValue) {
const oldValue = this._vote;
if (newValue === oldValue) {
return;
}
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
this._vote = newValue;
this.setAttribute('vote', newValue);
}
get vote() {
return this._vote;
}
Inicializa la propiedad de la instancia _vote
con null
en constructor
y, en el método set, verificas si el valor nuevo es diferente. Si es así, ajusta la calificación según corresponda y, lo que es más importante, refleja el atributo vote
en el host con this.setAttribute
.
A continuación, configura la vinculación de atributos:
index.js
static get observedAttributes() {
return ['rating', 'vote'];
}
attributeChangedCallback(attributeName, oldValue, newValue) {
if (attributeName === 'rating') {
const newRating = Number(newValue);
this.rating = newRating;
} else if (attributeName === 'vote') {
this.vote = newValue;
}
}
Una vez más, este es el mismo proceso que realizaste con la vinculación de atributo rating
. agregas vote
a observedAttributes
y estableces la propiedad vote
en attributeChangedCallback
. Y, ahora, por último, agrega algunos objetos de escucha de eventos de clic para darles la funcionalidad a los botones.
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
this._boundOnUpClick = this._onUpClick.bind(this);
this._boundOnDownClick = this._onDownClick.bind(this);
}
connectedCallback() {
...
this.shadowRoot.querySelector('.thumb_up')
.addEventListener('click', this._boundOnUpClick);
this.shadowRoot.querySelector('.thumb_down')
.addEventListener('click', this._boundOnDownClick);
}
disconnectedCallback() {
this.shadowRoot.querySelector('.thumb_up')
.removeEventListener('click', this._boundOnUpClick);
this.shadowRoot.querySelector('.thumb_down')
.removeEventListener('click', this._boundOnDownClick);
}
_onUpClick() {
this.vote = 'up';
}
_onDownClick() {
this.vote = 'down';
}
En el constructor
, vinculas algunos objetos de escucha de clics al elemento y mantienes las referencias. En el connectedCallback
, escuchas los eventos de clic en los botones. En el disconnectedCallback
, limpia estos objetos de escucha y, en los mismos objetos de clic, configura vote
de manera correcta.
Felicitaciones, ahora tienes un Web Component con todas las funciones. intenta hacer clic en algunos botones. El problema ahora es que mi archivo JS ahora alcanza 96 líneas, mi archivo HTML 43 líneas, y el código es bastante detallado y es imperativo para un componente tan simple. Aquí es donde entra en juego el proyecto de Lit de Google.
7. HTML de Lit
Punto de control del código
¿Por qué lit-html?
En primer lugar, la etiqueta <template>
es útil y eficiente, pero no está empaquetada con la lógica del componente, lo que dificulta la distribución de la plantilla con el resto de la lógica. Además, la forma en que se usan los elementos de la plantilla se presta de manera inherente al código imperativo, que, en muchos casos, conduce a un código menos legible en comparación con patrones de programación declarativos.
Aquí es donde entra en juego lit-html. El lenguaje HTML de Lit es el sistema de renderización de Lit que te permite escribir plantillas HTML en JavaScript y, luego, renderizar y volver a representar eficazmente esas plantillas junto con los datos para crear y actualizar el DOM. Es similar a las bibliotecas JSX y VDOM populares, pero se ejecuta de forma nativa en el navegador y mucho más eficiente en muchos casos.
Cómo usar el HTML de Lit
A continuación, migra el componente web nativo rating-element
para usar la plantilla de Lit que usa literales de plantillas etiquetados, que son funciones que toman strings de plantilla como argumentos con una sintaxis especial. Luego, Lit usa elementos de plantilla de forma interna para proporcionar una renderización rápida y algunas funciones de limpieza para la seguridad. Para comenzar, migra <template>
en index.html
a una plantilla de Lit. Para ello, agrega un método render()
al componente web:
index.js
// Dont forget to import from Lit!
import {render, html} from 'lit';
class RatingElement extends HTMLElement {
...
render() {
if (!this.shadowRoot) {
return;
}
const template = html`
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
</style>
<button class="thumb_down">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>`;
render(template, this.shadowRoot);
}
}
También puedes borrar tu plantilla de index.html
. En este método de renderización, debes definir una variable llamada template
y, luego, invocar la función literal de plantilla etiquetada html
. También notarás que realizaste una vinculación de datos simple dentro del elemento span.rating
con la sintaxis de interpolación literal de plantilla de ${...}
. Esto significa que, con el tiempo, ya no necesitarás actualizar ese nodo de manera imperativa. Además, debes llamar al método render
lit, que renderiza la plantilla de forma síncrona en la shadow root.
Migra a la sintaxis declarativa
Ahora que te deshiciste del elemento <template>
, refactoriza el código para llamar al método render
recién definido. Puedes comenzar aprovechando la vinculación del objeto de escucha de eventos de lit para borrar el código del objeto de escucha:
index.js
<button
class="thumb_down"
@click=${() => {this.vote = 'down'}}>
...
<button
class="thumb_up"
@click=${() => {this.vote = 'up'}}>
Las plantillas de Lit pueden agregar un objeto de escucha de eventos a un nodo con la sintaxis de vinculación @EVENT_NAME en la que, en este caso, actualizas la propiedad vote
cada vez que se hace clic en estos botones.
A continuación, borra el código de inicialización del objeto de escucha de eventos en constructor
, y en connectedCallback
y disconnectedCallback
:
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
}
connectedCallback() {
this.attachShadow({mode: 'open'});
this.render();
}
// remove disonnectedCallback and _onUpClick and _onDownClick
Pudiste quitar la lógica del objeto de escucha de clics de las tres devoluciones de llamada y también quitar el disconnectedCallback
por completo. También pudiste quitar todo el código de inicialización del DOM de connectedCallback
para que se vea mucho más elegante. Esto también significa que puedes deshacerte de los métodos de objeto de escucha _onUpClick
y _onDownClick
.
Por último, actualiza los establecedores de propiedades para usar el método render
, de modo que el dominio se pueda actualizar cuando cambien las propiedades o los atributos:
index.js
set rating(value) {
this._rating = value;
this.render();
}
...
set vote(newValue) {
const oldValue = this._vote;
if (newValue === oldValue) {
return;
}
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
this._vote = newValue;
this.setAttribute('vote', newValue);
// add render method
this.render();
}
Aquí, pudiste quitar la lógica de actualización del dominio del método set rating
y agregaste una llamada a render
desde el método set vote
. Ahora, la plantilla es mucho más legible, ya que puedes ver dónde se aplican las vinculaciones y los objetos de escucha de eventos.
Actualiza la página. Deberías ver un botón de calificación que funcione y debería verse de esta forma cuando se presione el voto a favor.
8. LitElement
Por qué LitElement
Aún hay algunos problemas en el código. Primero, si cambias la propiedad o el atributo vote
, es posible que cambie la propiedad rating
, lo que hará que se llame a render
dos veces. A pesar de que las llamadas repetidas de renderización son, básicamente, una no-op y son eficientes, la VM de JavaScript aún pasa tiempo llamando a esa función dos veces de forma síncrona. En segundo lugar, es tedioso agregar propiedades y atributos nuevos, ya que requiere mucho código estándar. Aquí es donde LitElement
entra en juego.
LitElement
es la clase base de Lit para crear componentes web rápidos y ligeros que se pueden usar en frameworks y entornos. A continuación, observa lo que LitElement
puede hacer por nosotros en rating-element
cambiando la implementación para usarla.
Usa LitElement
Comienza por importar y subclasificar la clase base LitElement
desde el paquete lit
:
index.js
import {LitElement, html, css} from 'lit';
class RatingElement extends LitElement {
// remove connectedCallback()
...
Importa LitElement
, que es la nueva clase base del rating-element
. A continuación, conservarás la importación de html
y, por último, css
, que nos permite definir literales de plantilla con etiquetas de CSS para las matemáticas de CSS, las plantillas y otros atributos de forma interna.
A continuación, mueve los estilos del método de renderización a la hoja de estilo estática de Lit:
index.js
class RatingElement extends LitElement {
static get styles() {
return css`
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
`;
}
...
Aquí es donde viven la mayoría de los estilos en Lit. Lit adoptará estos estilos y usará funciones del navegador, como Hojas de estilo constructibles, para proporcionar tiempos de renderización más rápidos y para pasarlo por el polyfill de componentes web en navegadores más antiguos, si es necesario.
Lifecycle
Lit presenta un conjunto de métodos de devolución de llamada de ciclo de vida de renderización además de las devoluciones de llamada de componentes web nativas. Estas devoluciones de llamada se activan cuando se cambian las propiedades declaradas de Lit.
Para usar esta función, debes declarar de forma estática qué propiedades activarán el ciclo de vida de la renderización.
index.js
static get properties() {
return {
rating: {
type: Number,
},
vote: {
type: String,
reflect: true,
}
};
}
// remove observedAttributes() and attributeChangedCallback()
// remove set rating() get rating()
Aquí, defines que rating
y vote
activarán el ciclo de vida de renderización de LitElement y definirán los tipos que se usarán para convertir los atributos de cadena en propiedades.
<user-profile .name=${this.user.name} .age=${this.user.age}>
${this.user.family.map(member => html`
<family-member
.name=${member.name}
.relation=${member.relation}>
</family-member>`)}
</user-profile>
Además, la marca reflect
de la propiedad vote
actualizará automáticamente el atributo vote
del elemento de host que activaste manualmente en el método set vote
.
Ahora que tienes el bloque de propiedades estáticas, puedes quitar toda la lógica de actualización de renderización de atributos y propiedades. Esto significa que puedes quitar los siguientes métodos:
connectedCallback
observedAttributes
attributeChangedCallback
rating
(métodos set y métodos get)vote
(métodos set y métodos get, pero mantienen la lógica de cambio del método set)
Lo que conservas es el constructor
y agregas un nuevo método de ciclo de vida de willUpdate
:
index.js
constructor() {
super();
this.rating = 0;
this.vote = null;
}
willUpdate(changedProps) {
if (changedProps.has('vote')) {
const newValue = this.vote;
const oldValue = changedProps.get('vote');
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
}
}
// remove set vote() and get vote()
Aquí, solo debes inicializar rating
y vote
, y mover la lógica del método set vote
al método de ciclo de vida willUpdate
. Se llama al método willUpdate
antes de render
cada vez que se cambia una propiedad de actualización, ya que LitElement agrupa los cambios de propiedad y hace que la renderización sea asíncrona. Los cambios en las propiedades reactivas (como this.rating
) en willUpdate
no activarán llamadas de ciclo de vida render
innecesarias.
Por último, render
es un método de ciclo de vida de LitElement que requiere que devuelvamos una plantilla de Lit:
index.js
render() {
return html`
<button
class="thumb_down"
@click=${() => {this.vote = 'down'}}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button
class="thumb_up"
@click=${() => {this.vote = 'up'}}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>`;
}
Ya no tienes que verificar la shadow root y no debes llamar a la función render
que se importó previamente desde el paquete 'lit'
.
Tu elemento debería renderizarse en la vista previa ahora. ¡haz clic!
9. Felicitaciones
Felicitaciones, compilaste con éxito un componente web desde cero y lo convertiste en un LitElement.
Lit es muy pequeño (menos de 5 KB comprimidos y en formato gzip), súper rápido y muy divertido de programar. Puedes hacer que los componentes sean consumidos por otros frameworks o puedes compilar aplicaciones completas con ellos.
Ahora sabes qué es un componente web, cómo crear uno y cómo Lit facilita su creación.
Punto de control del código
¿Quieres comparar tu código final con el nuestro? Compáralo aquí.
¿Qué sigue?
Consulta algunos de los otros codelabs.
- Lit para desarrolladores de React
- Crea un visor de ladrillos con lit-element
- Cómo crear un componente de Historias con lit-element
Lecturas adicionales
- Instructivo interactivo de Lit
- Los documentos de Lit
- Open Web Components: Es una comunidad de herramientas y orientación.
- WebComponents.dev: Crea un componente web en todos los marcos de trabajo conocidos.
Comunidad
- Lit and Friends Slack: La comunidad más grande de componentes web
- @buildWithLit en Twitter: La cuenta de Twitter del equipo que hizo Lit
- Web Components SF: reunión de Web Components para San Francisco