Una introducción completa a los componentes web en 2021
Todos tenemos proyectos en los que, en cambio, no trabajaríamos. El código se ha vuelto inmanejable, el alcance evolucionó, los arreglos rápidos se aplicaron sobre otros arreglos y la estructura colapsó bajo su peso de código espagueti. La codificación puede ser un asunto complicado.
Los proyectos se benefician del uso de módulos simples e independientes que tienen una sola responsabilidad. El código modular está encapsulado, por lo que hay menos necesidad de preocuparse por la implementación. Siempre que sepa lo que generará un módulo cuando se le proporcione un conjunto de entradas, no es necesario que comprenda cómo logró ese objetivo.
Aplicar conceptos modulares a un solo lenguaje de programación es sencillo, pero el desarrollo web requiere una mezcla diversa de tecnologías. Los navegadores analizan HTML, CSS y JavaScript para representar el contenido, los estilos y la funcionalidad de la página.
No siempre se mezclan fácilmente porque:
- El código relacionado se puede dividir en tres o más archivos, y
- Los estilos globales y los objetos de JavaScript pueden interferir entre sí de formas inesperadas.
Estos problemas se suman a los encontrados por los tiempos de ejecución del lenguaje, los marcos, las bases de datos y otras dependencias utilizadas en el servidor.
¿Qué son los componentes web?
Un componente web es una forma de crear un bloque de código encapsulado de responsabilidad única que se puede reutilizar en cualquier página.
Considere el HTML <video> etiqueta. Dada una URL, un espectador puede usar controles como reproducir, pausar, retroceder, avanzar y ajustar el volumen.
Se proporcionan estilo y funcionalidad, aunque puede realizar modificaciones utilizando varios atributos y llamadas a la API de JavaScript. Cualquier numero de <video> los elementos se pueden colocar dentro de otras etiquetas y no entrarán en conflicto.
¿Qué sucede si necesita su propia funcionalidad personalizada? Por ejemplo, ¿un elemento que muestra el número de palabras en la página? No hay HTML <wordcount> etiqueta (todavía).
Los marcos como React y Vue.js permiten a los desarrolladores crear componentes web donde el contenido, el estilo y la funcionalidad se pueden definir en un solo archivo JavaScript. Estos resuelven muchos problemas de programación complejos, pero tenga en cuenta que:
- Debe aprender a usar ese marco y actualizar su código a medida que evoluciona.
- Un componente escrito para un marco rara vez es compatible con otro.
- Los marcos aumentan y disminuyen en popularidad. Se volverá dependiente de los caprichos y prioridades del equipo de desarrollo y los usuarios.
- Los componentes web estándar pueden agregar funcionalidad al navegador, lo que es difícil de lograr solo en JavaScript (como Shadow DOM).
Afortunadamente, los conceptos populares que se introducen en las bibliotecas y los marcos suelen incorporarse a los estándares web. Ha llevado algo de tiempo, pero los componentes web han llegado.
Una breve historia de los componentes web
Después de muchos comienzos en falso específicos de proveedores, Alex Russell presentó por primera vez el concepto de componentes web estándar en la Conferencia Fronteers en 2011. La biblioteca de polímeros de Google (un polyfill basado en las propuestas actuales) llegó dos años después, pero las implementaciones tempranas no aparecieron. en Chrome y Safari hasta 2016.
Los proveedores de navegadores se tomaron su tiempo para negociar los detalles, pero los componentes web se agregaron a Firefox en 2018 y Edge en 2020 (cuando Microsoft cambió al motor Chromium).
Es comprensible que pocos desarrolladores hayan querido o hayan podido adoptar componentes web, pero finalmente hemos alcanzado un buen nivel de compatibilidad con navegadores con API estables. No todo es perfecto, pero son una alternativa cada vez más viable a los componentes basados en marcos.
Incluso si no está dispuesto a deshacerse de su favorito todavía, los componentes web son compatibles con cada framework, y las API serán compatibles durante los próximos años.
Los repositorios de componentes web prediseñados están disponibles para que todos los vean:
… ¡Pero escribir tu propio código es más divertido!
Este tutorial proporciona una introducción completa a los componentes web escritos sin un marco de JavaScript. Aprenderás qué son y cómo adaptarlos a tus proyectos web. Necesitará algunos conocimientos de HTML5, CSS y JavaScript.
Introducción a los componentes web
Los componentes web son elementos HTML personalizados como <hello-world></hello-world>. El nombre debe contener un guión para que nunca entre en conflicto con elementos admitidos oficialmente en la especificación HTML.
Debe definir una clase ES2015 para controlar el elemento. Puede tener cualquier nombre, pero HelloWorld es una práctica común. Debe extender la interfaz HTMLElement, que representa las propiedades y métodos predeterminados de cada elemento HTML.
Nota: Firefox te permite extender elementos HTML específicos como HTMLParagraphElement, HTMLImageElement o HTMLButtonElement. Esto no es compatible con otros navegadores y no le permite crear un Shadow DOM.
Para hacer algo útil, la clase requiere un método llamado connectedCallback () que se invoca cuando el elemento se agrega a un documento:
class HelloWorld extends HTMLElement {
// connect component
connectedCallback() {
this.textContent="Hello World!";
}
}
En este ejemplo, el texto del elemento se establece en «Hola mundo».
La clase debe estar registrada con CustomElementRegistry para definirla como un controlador para un elemento específico:
customElements.define( 'hello-world', HelloWorld );
El navegador ahora asocia el <hello-world> elemento con tu Hola Mundo class cuando su JavaScript está cargado (p. ej. <script type="module" src="https://kinsta.com/blog/web-components/./helloworld.js"></script>).
¡Ahora tienes un elemento personalizado!
Demostración de CodePen
Este componente se puede diseñar en CSS como cualquier otro elemento:
hello-world {
font-weight: bold;
color: red;
}
Agregar atributos
Este componente no es beneficioso ya que se genera el mismo texto independientemente. Como cualquier otro elemento, podemos agregar atributos HTML:
<hello-world name="Craig"></hello-world>
Esto podría anular el texto, por lo que «¡Hola, Craig!» se visualiza. Para lograr esto, puede agregar un constructor() función a la Hola Mundo class, que se ejecuta cuando se crea cada objeto. Debería:
- llama a súper() método para inicializar el elemento principal HTMLE, y
- realizar otras inicializaciones. En este caso, definiremos un nombre propiedad que se establece en un valor predeterminado de «Mundo»:
class HelloWorld extends HTMLElement {
constructor() {
super();
this.name="World";
}
// more code...
Su componente solo se preocupa por nombre atributo. Una estática Observados Atributos () La propiedad debe devolver una matriz de propiedades para observar:
// component attributes
static get observedAttributes() {
return ['name'];
}
Un atributoChangedCallback () Se llama al método cuando se define un atributo en el HTML o se cambia mediante JavaScript. Se pasa el nombre de la propiedad, el valor anterior y el nuevo valor:
// attribute change
attributeChangedCallback(property, oldValue, newValue) {
if (oldValue === newValue) return;
this[ property ] = newValue;
}
En este ejemplo, solo el nombre la propiedad nunca se actualizaría, pero puede agregar propiedades adicionales según sea necesario.
Finalmente, necesita modificar el mensaje en el connectedCallback () método:
// connect component
connectedCallback() {
this.textContent = `Hello ${ this.name }!`;
}
Demostración de CodePen
Métodos de ciclo de vida
El navegador llama automáticamente a seis métodos a lo largo del ciclo de vida del estado del componente web. La lista completa se proporciona aquí, aunque ya ha visto los primeros cuatro en los ejemplos anteriores:
constructor()
Se llama cuando el componente se inicializa por primera vez. Debe llamar súper() y puede establecer cualquier valor predeterminado o realizar otros procesos de reproducción previa.
atributos observados estáticos ()
Devuelve una matriz de atributos que observará el navegador.
atributoChangedCallback (propertyName, oldValue, newValue)
Se llama cada vez que se cambia un atributo observado. Los definidos en HTML se pasan inmediatamente, pero JavaScript puede modificarlos:
document.querySelector('hello-world').setAttribute('name', 'Everyone');
Es posible que el método deba activar una nueva renderización cuando esto ocurra.
connectedCallback ()
Esta función se llama cuando el componente web se adjunta a un modelo de objetos de documento. Debería ejecutar cualquier renderizado requerido.
desconectadoCallback ()
Se llama cuando el componente web se elimina de un modelo de objetos de documento. Esto puede resultar útil si necesita realizar una limpieza, como eliminar el estado almacenado o cancelar las solicitudes Ajax.
adoptadoCallback ()
Esta función se llama cuando un componente web se mueve de un documento a otro. Puede encontrar un uso para esto, ¡aunque me ha costado pensar en algún caso!
Cómo interactúan los componentes web con otros elementos
Los componentes web ofrecen algunas funciones únicas que no encontrará en los marcos de JavaScript.
El DOM de la Sombra
Si bien el componente web que hemos creado anteriormente funciona, no es inmune a la interferencia externa, y CSS o JavaScript podrían modificarlo. Del mismo modo, los estilos que defina para su componente podrían filtrarse y afectar a otros.
Shadow DOM resuelve este problema de encapsulación adjuntando un DOM separado a la Web Componente con:
const shadow = this.attachShadow({ mode: 'closed' });
El modo puede ser:
- «abierto» – JavaScript en la página exterior puede acceder a Shadow DOM (usando Element.shadowRoot), o
- «cerrado» – Solo se puede acceder al Shadow DOM dentro del Componente Web.
El Shadow DOM se puede manipular como cualquier otro elemento DOM:
connectedCallback() {
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
<style>
p {
text-align: center;
font-weight: normal;
padding: 1em;
margin: 0 0 2em 0;
background-color: #eee;
border: 1px solid #666;
}
</style>
<p>Hello ${ this.name }!</p>`;
}
El componente ahora muestra el texto «Hola» dentro de un <p> elemento y le da estilo. No puede ser modificado por JavaScript o CSS fuera del componente, aunque algunos estilos como la fuente y el color se heredan de la página porque no fueron definidos explícitamente.
Demostración de CodePen
Los estilos incluidos en este componente web no pueden afectar a otros párrafos de la página o incluso a otros <hello-world> componentes.
Tenga en cuenta que el CSS :host El selector puede diseñar el exterior <hello-world> elemento desde dentro del Componente Web:
:host {
transform: rotate(180deg);
}
También puede establecer estilos para que se apliquen cuando el elemento usa una clase específica, por ejemplo <hello-world class="rotate90">:
:host(.rotate90) {
transform: rotate(90deg);
}
Plantillas HTML
Definir HTML dentro de un script puede resultar poco práctico para componentes web más complejos. Una plantilla le permite definir un fragmento de HTML en su página que su componente web puede usar. Esto tiene varios beneficios:
- Puede modificar el código HTML sin tener que reescribir cadenas dentro de su JavaScript.
- Los componentes se pueden personalizar sin tener que crear clases de JavaScript independientes para cada tipo.
- Es más fácil definir HTML en HTML, y se puede modificar en el servidor o en el cliente antes de que se procese el componente.
Las plantillas se definen en un <template> etiqueta, y es práctico asignar una ID para que pueda hacer referencia a ella dentro de la clase de componente. Este ejemplo de tres párrafos para mostrar el mensaje «Hola»:
<template id="hello-world">
<style>
p {
text-align: center;
font-weight: normal;
padding: 0.5em;
margin: 1px 0;
background-color: #eee;
border: 1px solid #666;
}
</style>
<p class="hw-text"></p>
<p class="hw-text"></p>
<p class="hw-text"></p>
</template>
La clase Web Component puede acceder a esta plantilla, obtener su contenido y clonar los elementos para asegurarse de que está creando un fragmento DOM único en todos los lugares donde se usa:
const template = document.getElementById('hello-world').content.cloneNode(true);
El DOM se puede modificar y agregar directamente al Shadow DOM:
connectedCallback() {
const
shadow = this.attachShadow({ mode: 'closed' }),
template = document.getElementById('hello-world').content.cloneNode(true),
hwMsg = `Hello ${ this.name }`;
Array.from( template.querySelectorAll('.hw-text') )
.forEach( n => n.textContent = hwMsg );
shadow.append( template );
}
Demostración de CodePen
Ranuras para plantillas
Las ranuras te permiten personalizar una plantilla. Suponga que quería usar su <hello-world> Web Component pero coloque el mensaje dentro de un
encabezado en el DOM de la Sombra. Podrías escribir este código:
<hello-world name="Craig">
<h1 slot="msgtext">Hello Default!</h1>
</hello-world>
(Nota la espacio atributo.)
Opcionalmente, podría querer agregar otros elementos como otro párrafo:
<hello-world name="Craig">
<h1 slot="msgtext">Hello Default!</h1>
<p>This text will become part of the component.</p>
</hello-world>
Las ranuras ahora se pueden implementar dentro de su plantilla:
<template id="hello-world">
<slot name="msgtext" class="hw-text"></slot>
<slot></slot>
</template>
Un atributo de espacio de elemento establecido en «msgtext» (el <h1>) se inserta en el punto donde hay un <slot> llamado «msgtext». los <p> no tiene un nombre de espacio asignado, pero se usa en el siguiente disponible sin nombre <slot>. En efecto, la plantilla se convierte en:
<template id="hello-world">
<slot name="msgtext" class="hw-text">
<h1 slot="msgtext">Hello Default!</h1>
</slot>
<slot>
<p>This text will become part of the component.</p>
</slot>
</template>
En realidad, no es tan simple. A <slot> elemento en Shadow DOM puntos a los elementos insertados. Solo puede acceder a ellos localizando un <slot> luego usando el método .assignedNodes () para devolver una matriz de elementos secundarios internos. El actualizado connectedCallback () método:
connectedCallback() {
const
shadow = this.attachShadow({ mode: 'closed' }),
hwMsg = `Hello ${ this.name }`;
// append shadow DOM
shadow.append(
document.getElementById('hello-world').content.cloneNode(true)
);
// find all slots with a hw-text class
Array.from( shadow.querySelectorAll('slot.hw-text') )
// update first assignedNode in slot
.forEach( n => n.assignedNodes()[0].textContent = hwMsg );
}
Demostración de CodePen
Además, no puede diseñar directamente los elementos insertados, aunque puede apuntar a ranuras específicas dentro de su componente web:
<template id="hello-world">
<style>
slot[name="msgtext"] { color: green; }
</style>
<slot name="msgtext" class="hw-text"></slot>
<slot></slot>
</template>
Los espacios de plantilla son un poco inusuales, pero una ventaja es que su contenido se mostrará si JavaScript no se ejecuta. Este código muestra un encabezado y un párrafo predeterminados que solo se reemplazan cuando la clase Web Component se ejecuta correctamente:
<hello-world name="Craig">
<h1 slot="msgtext">Hello Default!</h1>
<p>This text will become part of the component.</p>
</hello-world>
Por lo tanto, puede implementar alguna forma de mejora progresiva, incluso si es solo una «Necesitas JavaScript» ¡mensaje!
El DOM declarativo de la sombra
Los ejemplos anteriores construyen un Shadow DOM usando JavaScript. Esa sigue siendo la única opción, pero se está desarrollando un Shadow DOM declarativo experimental para Chrome. Esto permite la representación del lado del servidor y evita cualquier cambio de diseño o destellos de contenido sin estilo.
El siguiente código es detectado por el analizador HTML, que crea un Shadow DOM idéntico al que creó en la última sección (necesitaría actualizar el mensaje según sea necesario):
<hello-world name="Craig">
<template shadowroot="closed">
<slot name="msgtext" class="hw-text"></slot>
<slot></slot>
</template>
<h1 slot="msgtext">Hello Default!</h1>
<p>This text will become part of the component.</p>
</hello-world>
La función no está disponible en ningún navegador y no hay garantía de que llegue a Firefox o Safari. Puede obtener más información sobre el DOM Shadow declarativo, y un polyfill es simple, pero tenga en cuenta que la implementación podría cambiar.
Eventos de Shadow DOM
Su Web Component puede adjuntar eventos a cualquier elemento en Shadow DOM tal como lo haría en el DOM de la página, como para escuchar eventos de clic en todos los elementos secundarios internos:
shadow.addEventListener('click', e => {
// do something
});
A menos que detenga la propagación, el evento aparecerá en la página DOM, pero el evento se volverá a dirigir. Por lo tanto, parece provenir de su elemento personalizado en lugar de elementos dentro de él.
Cualquier componente web que cree funcionará en todos los marcos de JavaScript. Ninguno de ellos conoce ni se preocupa por los elementos HTML: su <hello-world> El componente se tratará de forma idéntica a un <div> y se coloca en el DOM donde se activará la clase.
custom-elements-everywhere.com proporciona una lista de marcos y notas de componentes web. La mayoría son totalmente compatibles, aunque React.js tiene algunos desafíos. Es posible usar <hello-world> en JSX:
import React from 'react';
import ReactDOM from 'react-dom';
import from './hello-world.js';
function MyPage() {
return (
<>
<hello-world name="Craig"></hello-world>
</>
);
}
ReactDOM.render(<MyPage />, document.getElementById('root'));
…pero:
- React solo puede pasar tipos de datos primitivos a atributos HTML (no matrices u objetos)
- React no puede escuchar eventos de componentes web, por lo que debe adjuntar manualmente sus propios controladores.
Críticas y problemas de componentes web
Los componentes web han mejorado significativamente, pero algunos aspectos pueden ser difíciles de administrar.
Dificultades de estilo
Aplicar estilos a los componentes web plantea algunos desafíos, especialmente si desea anular los estilos con ámbito. Hay muchas soluciones:
- Evite el uso de Shadow DOM. Puede agregar contenido directamente a su elemento personalizado, aunque cualquier otro JavaScript podría cambiarlo accidental o maliciosamente.
- Utilizar el
:hostclases. Como vimos anteriormente, CSS con ámbito puede aplicar estilos específicos cuando se aplica una clase al elemento personalizado. - Consulte las propiedades personalizadas de CSS (variables). Las propiedades personalizadas caen en cascada en los componentes web, por lo que, si su elemento usa
var(--my-color), puedes configurar--my-coloren un recipiente exterior (como:root) y se utilizará. - Aprovecha las sombras. El selector new :: part () puede diseñar un componente interno que tenga un atributo de parte, es decir
<h1 part="heading">dentro de una<hello-world>el componente se puede diseñar con el selectorhello-world::part(heading). - Pase una serie de estilos. Puede pasarlos como un atributo para aplicar dentro de un
<style>cuadra.
Ninguno es ideal y deberá planificar cuidadosamente cómo otros usuarios pueden personalizar su componente web.
Entradas ignoradas
Alguna <input>, <textarea>, o <select> los campos en su Shadow DOM no se asocian automáticamente dentro del formulario contenedor. Los primeros en adoptar componentes web agregarían campos ocultos al DOM de la página o usarían la interfaz FormData para actualizar los valores. Tampoco son particularmente prácticos y rompen la encapsulación de los componentes web.
La nueva interfaz ElementInternals permite que un componente web se conecte a formularios para que se puedan definir valores personalizados y validez. Está implementado en Chrome, pero hay un polyfill disponible para otros navegadores.
Para demostrarlo, creará un <input-age name="your-age"></input-age> componente. La clase debe tener una estática formAssociated valor establecido verdadero y, opcionalmente, un formAssociatedCallback () se puede llamar al método cuando la forma externa está asociada:
// <input-age> web component
class InputAge extends HTMLElement {
static formAssociated = true;
formAssociatedCallback(form) {
console.log('form associated:', form.id);
}
El constructor ahora debe ejecutar el attachInternals () método, que permite que el componente se comunique con el formulario y otro código JavaScript que quiera inspeccionar el valor o la validación:
constructor() {
super();
this.internals = this.attachInternals();
this.setValue('');
}
// set form value
setValue(v) {
this.value = v;
this.internals.setFormValue(v);
}
El elemento interno setFormValue () El método establece el valor del elemento para el formulario principal inicializado con una cadena vacía aquí (también se puede pasar un objeto FormData con múltiples pares de nombre / valor). Otras propiedades y métodos incluyen:
- formulario: el formulario principal
- etiquetas: una matriz de elementos que etiquetan el componente
- Opciones de API de validación de restricciones como willValidate, checkValidity y validationMessage
los connectedCallback () El método crea un Shadow DOM como antes, pero también debe monitorear el campo en busca de cambios, por lo que setFormValue () se puede ejecutar:
connectedCallback() {
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
<style>input { width: 4em; }</style>
<input type="number" placeholder="age" min="18" max="120" />`;
// monitor input values
shadow.querySelector('input').addEventListener('input', e => {
this.setValue(e.target.value);
});
}
Ahora puede crear un formulario HTML utilizando este componente web que actúa de manera similar a otros campos de formulario:
<form id="myform">
<input type="text" name="your-name" placeholder="name" />
<input-age name="your-age"></input-age>
<button>submit</button>
</form>
Funciona, pero es cierto que se siente un poco complicado.
Compruébalo en CodePen demostración
Para obtener más información, consulte este artículo sobre controles de formulario más capaces.
Resumen
Los componentes web han luchado por lograr un acuerdo y adopción en un momento en que los marcos de JavaScript han crecido en estatura y capacidad. Si viene de React, Vue o Angular, los componentes web pueden parecer complejos y torpes, especialmente cuando le faltan funciones como el enlace de datos y la administración de estado.
Hay problemas que resolver, pero el futuro de los componentes web es brillante. Son independientes del marco, livianos, rápidos y pueden implementar funcionalidades que serían imposibles en JavaScript solo.
Hace una década, pocos habrían abordado un sitio sin jQuery, pero los proveedores de navegadores tomaron las partes excelentes y agregaron alternativas nativas (como querySelector). Lo mismo sucederá con los marcos de JavaScript, y los componentes web es el primer paso tentativo.
¿Tiene alguna pregunta sobre cómo utilizar los componentes web? ¡Hablemos de ello en la sección de comentarios!
Ahorre tiempo, costos y maximice el rendimiento del sitio con:
- Ayuda instantánea de expertos en alojamiento de WordPress, 24 horas al día, 7 días a la semana.
- Integración de Cloudflare Enterprise.
- Alcance de audiencia global con 28 centros de datos en todo el mundo.
- Optimización con nuestro monitoreo de rendimiento de aplicaciones integrado.
Todo eso y mucho más, en un plan sin contratos a largo plazo, migraciones asistidas y una garantía de devolución de dinero de 30 días. Consulte nuestros planes o hable con ventas para encontrar el plan adecuado para usted.
