Skip to content

Cómo burbujean los eventos en los componentes web Lightning ☁️

Esta es una traducción que desde EGA Futura ofrecemos como cortesía a toda la Ohana y comunidad de programadores , consultores , administradores y arquitectos de Salesforce para toda Iberoamérica .

El enlace a la publicación original, lo encontrarás al final de este artículo.

Cuando está componiendo componentes web personalizados, necesita comprender cómo los eventos fluyen a través del DOM porque así es como los niños y los padres se comunican: apoyos hacia abajo, eventos hacia arriba.

Cuando un evento burbujea, se convierte en parte de la API de su componente y todos los consumidores a lo largo de la ruta del evento deben comprender el evento. Ya sea que esté componiendo un componente simple o complejo, es importante comprender cómo funciona el burbujeo, para que pueda elegir la configuración de burbujeo más restrictiva que funcione para su componente.

Este artículo cubre dos patrones de composición: estático y dinámico, que utiliza ranuras. En cada patrón, un componente secundario que se encuentra en la parte inferior de la composición envía un evento personalizado a través del árbol de componentes. Veré cómo el evento burbujea para cada configuración de bubbles y composed , para que comprenda profundamente cómo configurar eventos en sus composiciones.

NOTA :
Este artículo se aplica al trabajo con Lightning Web Components en Lightning Platform y Open Source. Debido a que Salesforce admite versiones de navegador más antiguas que no son totalmente compatibles con el DOM de sombra nativo, los componentes web Lightning en la plataforma Lightning utilizan una versión sintética del DOM de sombra. En código abierto, puede optar por utilizar DOM de sombra sintético o nativo. Cuando hay una diferencia de comportamiento entre los dos, lo llamamos. Debido a la sombra sintética, cuando usa Dev Tools para ver el marcado en un navegador, no ve la etiqueta #shadow-root

DOM de sombra

Si ya está familiarizado con el DOM de sombra y las ranuras, pase al primer patrón: Composición estática .

Los componentes web crean y distribuyen eventos DOM, pero hay dos cosas acerca de los componentes web que hacen que trabajar con eventos sea un poco diferente: DOM en la sombra y ranuras. Primero explicaré el DOM de la sombra y luego las ranuras.

El DOM de cada componente web está encapsulado en un DOM en la sombra que otros componentes no pueden ver. Cuando un evento burbujea ( bubbles = true ), no cruza un límite de sombra a menos que lo configure en ( composed = true ) .

Shadow DOM permite la encapsulación de componentes. Permite que un componente tenga su propio árbol de sombra de nodos DOM al que no se puede acceder accidentalmente desde el documento principal y puede tener reglas de estilo locales.

La raíz de sombra es el nodo superior en un árbol de sombra. Este nodo está adjunto a un nodo DOM regular llamado elemento host.

El límite de la sombra es la línea entre la raíz de la sombra y el elemento anfitrión. Es donde termina el DOM en la sombra y comienza el DOM normal. Las consultas DOM y las reglas CSS no pueden cruzar el límite de sombra, lo que crea encapsulación. El DOM regular también se llama DOM ligero para distinguirlo del DOM sombra.

Si un DOM es un DOM ligero o un DOM de sombra depende del punto de vista.

  • Desde el punto de vista de la clase JavaScript de un componente, los elementos de su plantilla pertenecen al DOM ligero. El componente los posee; son elementos DOM regulares.
  • Desde el punto de vista del mundo exterior, esos mismos elementos son parte del DOM de sombra del componente. El mundo exterior no puede verlos ni acceder a ellos.
 <!-- flattened DOM --> <body> <p> I'm just a regular paragraph element, I'm part of the light DOM </p> <!-- The c-child element is part of the light DOM. But everything below #shadow-root is hidden, because it's part of c-child's shadow DOM. --> <c-child> #shadow-root <p> En c-child, I'm light DOM. To everyone else, I'm shadow DOM. </p> </c-child> </body>

Ranuras

Un componente web puede contener elementos <slot></slot> . Otros componentes pueden pasar elementos a una ranura, lo que le permite componer componentes de forma dinámica.

Cuando un componente tiene una <slot></slot> , un componente contenedor puede pasar elementos DOM ligeros a la ranura.

 <!-- childSlot.html --> <template> <h2> I can host other elements via slots </h2> <slot></slot> </template> <!-- container.html --> <template> <c-child-slot> <p> Passing you some Light DOM content. </p> </c-child-slot> </template>

El navegador muestra el árbol aplanado , que es lo que ve en la página. Lo importante a entender es que el DOM que se pasa a la ranura no se convierte en parte del DOM en la sombra del niño; es parte del DOM de sombra del contenedor.

 <!-- flattened DOM --> <cuerpo> <c-container> #shadow-root <c-child-slot> #shadow-root  <h2> I can host other elements via slots </h2>  <slot> // To the outside world, // I'm not part of c-child-slot shadow DOM // I'm part of c-container shadow DOM <p> To c-container, I'm light DOM. </p> </slot> </c-child-slot> </c-container> </body>

Cuando usamos tragamonedas, aunque el contenido parece estar renderizado dentro del elemento de la ranura, el elemento real no se mueve. Más bien, se inserta un "puntero" al contenido original en la ranura. Este es un concepto importante de entender para poder entender lo que está sucediendo con nuestros eventos.

Eventos

Para crear eventos en un componente web Lightning, use la CustomEvent , que hereda de Event . En Lightning Web Components, CustomEvent proporciona una experiencia más consistente en todos los navegadores, incluido Internet Explorer.

Cuando cree un evento, defina el comportamiento de propagación del evento utilizando dos propiedades: bubbles y composed .

bubbles
Un valor booleano que indica si el evento fluye a través del DOM o no. El valor predeterminado es false .

composed
Un valor booleano que indica si el evento puede atravesar el límite de sombra. El valor predeterminado es false .

 importar {LightningElement} de 'lwc'; exportar clase predeterminada MyComponent extiende LightningElement { renderingCallback () { this.dispatchEvent ( new CustomEvent ('notificar', {burbujas: verdadero, compuesto: verdadero}) ); }
}

Importante
Los eventos pasan a formar parte de la API de su componente, por lo que es mejor utilizar la configuración menos disruptiva y más restrictiva que funcione para su caso de uso.

Para obtener información sobre un evento, utilice la API de Event

  • event.target : una referencia al elemento que distribuyó el evento. A medida que sube por el árbol, el valor del target cambia para representar un elemento en el mismo ámbito que el elemento de escucha. Este retargeting de eventos conserva la encapsulación de componentes. Veremos cómo funciona esto más adelante.
  • event.currentTarget : una referencia al elemento al que está adjunto el controlador de eventos.
  • event.composedPath() : interfaz que devuelve la ruta del evento, que es una matriz de los objetos en los que se invocarán los oyentes, según la configuración utilizada.

Composición estática

Una composición estática no usa ranuras. Aquí tenemos el ejemplo más simple: c-app compone el componente c-parent , que a su vez compone c-child .

 <! - cuerpo ->
<c-app onbuttonclick = {handleButtonClick> </c-app>
 <! - aplicación ->
<plantilla> <h2> Mi aplicación </h2> <c-padre onbuttonclick = {handleButtonClick}> </c-parent>
</template>
 <! - parent.html ->
<plantilla> <h3> Soy un componente principal </h3> <div class = 'envoltorio' onbuttonclick = {handleButtonClick}> <c-niño onbuttonclick = {handleButtonClick}> </c-child> </div>
</template>
 <! - niño.html ->
<plantilla> <h3> Soy un componente c-child </h3> <button onclick = {handleClick}> haz clic en mí </button>
</template>

buttonclick un evento, buttonclick, de c-child cada vez que ocurre una acción de click en su elemento de button Hemos adjuntado detectores de eventos para ese evento personalizado en los siguientes elementos:

  • body
  • host de c-app
  • anfitrión c-parent
  • div.wrapper
  • anfitrión c-child

El árbol aplanado se ve así:

 <body> <! - Escuchando el evento "buttonclick" -> <c-app> <! - Escuchando el evento "buttonclick" -> # raíz-sombra <h2> Mi aplicación </h2> <c-parent> <! - Escuchando el evento "buttonclick" -> # raíz-sombra <h3> Soy un componente principal </h3> <div> <! - Escuchando el evento "buttonclick" -> <c-child> <! - Escuchando el evento "buttonclick" -> # raíz-sombra <h3> Soy un componente c-child </h3> <button> haz clic en mí </button> # / raíz-sombra </c-child> </div> # / raíz-sombra </c-parent> # / raíz-sombra </c-app>
</body>

Aquí hay una representación visual:

Ahora veremos cómo el evento burbujea con la configuración de cada evento.

{burbujas: falso, compuesto: falso}

Con esta configuración, solo c-child puede reaccionar al buttonclick disparado desde c-child . El evento no pasa por el anfitrión. Esta es la configuración recomendada porque proporciona la mejor encapsulación para su componente. Aquí es donde comienza, luego desde aquí puede comenzar a incorporar otras configuraciones más permisivas, como las que estamos a punto de explorar en las próximas secciones, para adaptarse a sus requisitos.

Si inspeccionamos el controlador c-child , encontramos estos valores en el objeto de event

  • event.currentTarget = c-child
  • event.target = c-child

{burbujas: verdadero, compuesto: falso}

Con esta configuración, el evento buttonclick c-child viaja de abajo hacia arriba hasta que encuentra una raíz oculta o el evento se cancela. El resultado, además de c-child , div.wrapper también puede reaccionar al evento.

Utilice esta configuración para generar un evento dentro de la plantilla del componente, creando un evento interno. También puede usar esta configuración para manejar un evento en el abuelo de un componente.


Y nuevamente, esto es lo que nos dicen los eventos para cada controlador:

c-child manejador de niños:

  • event.currentTarget = c-child
  • event.target = c-child

controlador div.childWrapper

  • event.currentTarget = div.childWrapper
  • event.target = c-child

{burbujas: falso, compuesto: verdadero}

Esta configuración es compatible con Shadow DOM nativo, lo que significa que no es compatible con Lightning Platform. Incluso para el código abierto de LWC, esta configuración no se sugiere, pero es útil para comprender cómo los eventos burbujean en un contexto DOM en la sombra.

Los eventos compuestos pueden romper los límites de las sombras y rebotar de un host a otro a lo largo de su trayectoria. No continúan burbujeando más allá de eso a menos que también establezcan bubbles:true .

En este caso, c-child , c-parent y c-app pueden reaccionar al evento. Es interesante notar que div.wrapper no puede manejar el evento, porque el evento no burbujea en la sombra.

Veamos lo que el controlador tiene que decir sobre el evento:

c-child manejador de niños:

  • event.currentTarget = c-child
  • event.target = c-child

controlador c-parent

  • event.currentTarget = c-parent
  • event.target = c-parent

controlador de c-app

  • event.currentTarget = c-app
  • event.target = c-app

Es interesante notar que div.wrapper no puede manejar el evento porque incluso si el evento se propaga de una sombra a otra, no burbujea en la sombra en sí.

Hagamos una pausa aquí y observemos que aunque el evento fue disparado desde c-child , cuando llega a c-parent y c-app , muestra el host como target y currentTarget .

¿Qué esta pasando? Reorientación de eventos en su máxima expresión. A medida que el evento buttonclick c-child , el evento se trata como un detalle de implementación y su target se cambia para que coincida con el alcance del oyente.

Esta es una de las razones por las que las composed:true deben usarse con precaución, ya que la semántica del c-child y su receptor no coinciden. c-child disparó el evento, pero para c-app , parece que c-app activó.

Usar la { bubbles:false, composed:true } es un anti-patrón. El patrón correcto es para receptores que pueden entender el buttonclick de botón para volver a empaquetar y enviar el evento con la semántica adecuada. Por ejemplo, c-parent podría recibir el evento de c-child y exponer un nuevo evento personalizado, para que los elementos en el árbol de luz de c-app

{burbujas: verdadero, compuesto: verdadero}

Esta configuración no se sugiere porque crea un evento que cruza todos los límites. Cada elemento recibe el evento, incluso los elementos DOM regulares que no forman parte de ninguna sombra. El evento puede burbujear hasta el elemento del cuerpo.

Al disparar eventos de esta manera, puede contaminar el espacio del evento, filtrar información y crear semánticas confusas. Los eventos se consideran parte de la API de su componente, así que asegúrese de que cualquier persona en la ruta del evento pueda comprender y manejar la carga útil del evento, si la tiene.

Finalmente, exploremos los valores del evento:

c-child manejador de niños:

  • event.currentTarget = c-child
  • event.target = c-child

manejador div.wrapper

  • event.currentTarget = div.wraper
  • event.target = c-child

controlador c-parent

  • event.currentTarget = c-parent
  • event.target = c-parent

controlador de c-app

  • event.currentTarget = c-app
  • event.target = c-app

manipulador del body

  • event.currentTarget = body
  • event.target = c-app

Composición dinámica con ranuras

Ahora exploraremos cómo los eventos burbujean en composiciones que usan espacios. Tenemos un c-parent que acepta cualquier contenido a través del elemento especial <slot> . Usando c-app , componimos c-parent y pasamos c-child como su hijo.

 <! - cuerpo ->
<c-app> </c-app>
 <! - app.html ->
<plantilla> <h2> Mi aplicación </h2> <div class = 'envoltorio'> <c-padre> <c-child> </c-child> </c-parent> </div>
</template>
 <! - parent.html ->
<plantilla> <h3> Soy un componente principal </h3> <slot> </slot>
</template>
 <! - niño ->
<plantilla> <h3> Soy un componente secundario </h3> <button onclick = {handleClick}> haz clic en mí </button>
</template>

El código dispara un evento de c-child llamado buttonclick :

 handleClick () { const buttonclicked = new CustomEvent ('buttonclick', {// opciones de evento}); this.dispatchEvent (botón clic);
}

El código adjunta detectores de eventos en los siguientes elementos:

  • anfitrión c-child
  • slot
  • anfitrión c-parent
  • div.wrapper
  • host de c-app
  • body

Este es el árbol aplanado tal como se ve en las herramientas de desarrollo del navegador.

 <cuerpo> <c-app> <! - Escuchando el evento "buttonclick" -> # raíz-sombra <h2> Mi aplicación </h2> <div class = 'envoltorio'> <c-parent> <! - Escuchando el evento "buttonclick" -> # raíz-sombra <h3> Soy un componente principal </h3> <slot> <! - puntero a c-child -> </slot> # / raíz-sombra <c-child> <! - Escuchando el evento "buttonclick" -> # raíz-sombra <h3> Soy un componente secundario </h3> <button> haz clic en mí </button> # / raíz-sombra </c-child> </c-parent> </div> </c-app>
</body>

Recuerde que cuando usamos ranuras, aunque el contenido parece estar renderizado dentro del elemento de la ranura, el elemento real no se mueve. Más bien, se inserta un "puntero" al contenido original en la ranura. Este es un concepto importante de entender para poder entender lo que está sucediendo con nuestros eventos.

Esta vista del árbol aplanado le muestra dónde se encuentra realmente el contenido pasado en la ranura en el DOM. c-child es parte del DOM de sombra c-ap No es parte del DOM en la sombra c-parent

Con eso en mente, veamos los resultados que obtenemos con todas las diferentes configuraciones.

{burbujas: falso, compuesto: falso}

Al igual que con el ejemplo de composición anterior, el evento no pasa de c-child , que es donde se disparó. Esta es también la configuración recomendada para composiciones dinámicas porque proporciona la mejor encapsulación para su componente.

c-child manejador de niños:

  • event.currentTarget = c-child
  • event.target = c-child

{burbujas: verdadero, compuesto: falso}

NOTA : Esta configuración se comporta de manera diferente cuando está utilizando Shadow DOM nativo con Lightning Web Components: Open Source. Con el DOM de sombra nativo, el evento no sale de la ranura a menos que la composed también sea true .

Con esta configuración, el connectedchild disparado desde el evento c-child viaja de abajo hacia arriba hasta que encuentra una raíz oculta o el evento se cancela.

En nuestro ejemplo estático, mencionamos cómo un evento con esta configuración burbujea hasta que viaja de abajo hacia arriba hasta que encuentra una raíz de sombra o el evento se cancela. Exploremos lo que event.composedPath() tiene que decir sobre este caso de uso:

 0: mi-hijo
1: ranura
2: fragmento de documento // raíz de sombra de mi padre
3: mi padre
4: envoltura div.
5: fragmento de documento // raíz de la sombra de mi aplicación

Como puede ver, parece que el evento podría salir de #shadow-root my-parent y aparecer en el exterior, a pesar de que, composed se establece en false . ¿Confundido? Usted debería ser. Miremos más de cerca.

Aquí hay un efecto de "doble burbuja". Debido a que el contenido ranurado no mueve realmente las cosas, sino que crea punteros desde el DOM ligero y hacia un espacio en particular, cuando buttonclick evento buttonclick, el evento viaja desde ambos lugares, lo que crea una composedPath como la que acabamos de ver. .

Finalmente, esto es lo que nuestros controladores de eventos nos dicen sobre los objetivos:

c-child manejador de niños:

  • event.currentTarget = c-child
  • event.target = c-child

controlador c-parent

  • event.currentTarget = c-parent
  • event.target = c-child

manejador div.wrapper

  • event.currentTarget = div
  • event.target = c-child

Observe cómo el objetivo de div.wrapper c-child , ya que técnicamente el evento nunca cruzó la shadow root c-parent .

{burbujas: falso, compuesto: verdadero}

Al igual que la composición simple, esta configuración es un anti-patrón y no es compatible con Lightning Platform, pero es útil para comprender cómo los eventos burbujean en un contexto DOM en la sombra.

El evento rebota de un anfitrión a otro siempre que estén en diferentes sombras. En este caso, c-child no forma parte de c-parent , sino que forma parte de la sombra de c-app , por lo que salta directamente a c-app .

Los resultados de event.composedPath() también arrojan algunos resultados interesantes:

0: my-child
1: slot
2: document-fragment
3: my-parent
4: div.wrapper
5: document-fragment
6: my-test
7: body
8: html
9: document
10: Window

Muestra que cuando establecemos composed:true , el evento puede salir de cada raíz de sombra. En este caso, no es así, ya que la bubbles se establece en true . En cambio, salta de un host a otro.

Objetivos:

c-child manejador de niños:

  • event.currentTarget = c-child
  • event.target = c-child

controlador de c-app

  • event.currentTarget = c-app
  • event.target = c-child

Nuevamente, es útil mirar esta vista del árbol aplanado para recordar que c-child en la slot es solo un puntero.

{burbujas: verdadero, compuesto: verdadero}

Este es el escenario de "fuerza bruta" y, como nuestro ejemplo de composición estática, el evento burbujea por todas partes, lo cual es un anti-patrón. Cada elemento en la ruta compuesta del evento puede manejar el evento.

c-child manejador de niños:

  • event.currentTarget = c-child
  • event.target = c-child

controlador c-parent

  • event.currentTarget = c-parent
  • event.target = c-child

manejador div.wrapper

  • event.currentTarget = div
  • event.target = c-child

controlador de c-app

  • event.currentTarget = c-app
  • event.target = c-app

manipulador del body

  • event.currentTarget = body
  • event.target = c-app

Conclusión

Los eventos son parte de su API. Tenga en cuenta a los consumidores de la API. Cada consumidor a lo largo de la ruta del evento debe comprender el evento y cómo manejar su carga útil.

Tenga cuidado al configurar las composed y bubbles , ya que pueden tener resultados no deseados. Utilice la configuración menos disruptiva que funcione para su caso de uso, con el ser menos disruptivo ( bubbles: false, composed: false ).

Y finalmente, recuerde que las composiciones dinámicas que usan tragamonedas introducen un nivel adicional de complejidad ya que tiene que lidiar con los nodos asignados dentro de los elementos de su tragamonedas.

Sobre el Autor

Gonzalo Cordero trabaja como ingeniero de software en Salesforce.
Github: @gonzalocordero

Esta es una traducción realizada por EGA Futura, y este es el link a la publicación original: https://developer.salesforce.com/blogs/2021/08/how-events-bubble-in-lightning-web-components.html

Últimas novedades 
de EGA Futura
1954
Desde hace más de 25 años potenciamos a las Empresas de Iberoamérica
🎬 Video de EGA Futura » Conceptos de Seguridad (EGA Futura ERP / Salesforce)

🎬 Video de EGA Futura » Conceptos de Seguridad (EGA Futura ERP / Salesforce)

🎬 Video de Juan Manuel Garrido » Claves para tu Productividad diaria 🙌✅

🎬 Video de EGA Futura » Facturación Electrónica en Uruguay » Conceptos básicos con EGA Futura Windows

🎬 Video de EGA Futura » Facturación Electrónica en Uruguay » Configuración de EGA Futura Windows

🎬 Video de EGA Futura » Facturación Electrónica en Uruguay » Funcionamiento con EGA Futura Windows

🎬 Video de EGA Futura » Configuración de la Plataforma EGA Futura

🎬 Video de EGA Futura » Configuración de usuario en EGA Futura

🎬 Video de EGA Futura » Como automatizar la publicación en Redes Sociales?

🎬 Video de Juan Manuel Garrido » Cómo restaurar la configuración de fábrica de EGA Futura Windows sin perder la información

🎬 Video de Juan Manuel Garrido » Factura electrónica: Prueba de Factura Electronica previa a la activacion

🎬 Video de EGA Futura » Como se registran los Beneficios de cada Empleado en la base de datos de EGA Futura

🎬 Video de EGA Futura » EGA Futura Time Clock » Reloj de Control horario y asistencia

🎬 Video de EGA Futura » Como registrar Observaciones en un Empleado dentro de EGA Futura People?

🎬 Video de EGA Futura » Cómo registrar la Educación de cada Empleado en EGA Futura People?

🎬 Video de EGA Futura » Como hacer la Desvinculación de un Empleado? (Offboarding)

🎬 Video de EGA Futura » Como registrar Habilidades o Skills de empleados dentro de EGA Futura

🎬 Video de EGA Futura » Como hacer el Onboarding o Proceso de Incorporación de un Empleado?

🎬 Video de EGA Futura » Cómo administrar Turno de trabajo dentro de EGA Futura

🎬 Video de EGA Futura » Que es un Ticket interno dentro de la Plataforma EGA Futura

🎬 Video de EGA Futura » Que son los Entrenamientos de Empleado en EGA Futura people?

🎬 Video de EGA Futura » Qué son los Epics dentro de EGA Futura

🎬 Video de EGA Futura » Qué es EGA Futura People?

🎬 Video de EGA Futura » EGA Futura People » Asistencias

🎬 Video de EGA Futura » Soporte EGA Futura » Software de Gestión Windows vs Software de Gestión Nube 🤩

🎬 Video de EGA Futura » ツ Comparando un Objeto con un Fichero

Cómo burbujean los eventos en los componentes web Lightning ☁️
Cómo burbujean los eventos en los componentes web Lightning ☁️