WebComponents como frameworks, interacción de componentes

Cuando se trata de componentes web, a menudo dicen: “¿Qué quieres sin marcos? Todo está listo allí. De hecho, existen marcos creados en base a la implementación de los estándares incluidos en el grupo de componentes web. Incluso hay relativamente buenas como la X-Tag . Pero hoy, todavía entenderemos cuán simple, elegante y poderosa se ha vuelto la API del navegador para resolver las tareas de desarrollo cotidianas, incluida la organización de la interacción de los componentes entre sí y con otros objetos desde el contexto de la ejecución del navegador, y siempre puede usar marcos con componentes web, incluso aquellos que se desarrollaron a través de estándares, incluso a través de los mecanismos que analizaremos hoy, al menos como se indica en el sitio .

Decisiones como un reactivo nos enseñaron que debería haber un lado de big data y los componentes firmados para sus cambios, y en los componentes web también intentan discernir la ausencia de tal subsistema, de hecho, implica incluso un binning inverso inconsciente y ya existe en los componentes web.

Cada elemento tiene atributos del valor que podemos cambiar. Y si enumera los nombres en el enlace de atributos observados , cuando cambien, se llamará automáticamente a attributeChangedCallback () en el que podemos determinar el comportamiento del componente cuando el atributo cambia. Usando la magia getter, es fácil hacer la unión inversa de una manera similar.

Ya esbozamos un proyecto en la primera parte y hoy continuaremos reduciéndolo aún más.

Vale la pena mencionar de inmediato una limitación, al menos en la implementación actual, de que el valor del atributo solo puede ser un valor primitivo especificado por un literal y reducible a una cadena, pero en general es posible transferir "objetos" de esta manera usando comillas simples desde el exterior y dobles para definir valores y campos del proyecto.

<my-component my='{"foo": "bar"}'></my-component> 

Para usar este valor en el código, puede implementar un captador mágico automático que invocará JSON.parse () en él .

Pero por ahora, el valor numérico del contador es suficiente para nosotros.

Agregamos un nuevo atributo a nuestro elemento, lo especificamos como se observa, hacemos clic en el controlador de clic para incrementar este contador y agregamos la actualización del valor mostrado por enlace directo al gancho de cambio, cuya lógica se implementa en un método updateLabel () reutilizable por separado.

 export class MyWebComp extends HTMLElement { constructor() { super(); } connectedCallback() { let html = document.importNode(myWebCompTemplate.content, true); this.attachShadow({mode: 'open'}); this.shadowRoot.appendChild(html); this.updateLabel(); } updateLabel() { this.shadowRoot.querySelector('#helloLabel').textContent = 'Hello ' + this.getAttribute('greet-name') + ' ' + this.getAttribute('count'); } static get observedAttributes() { return ['count']; } attributeChangedCallback(name, oldValue, newValue) { if (name === 'count') { this.updateLabel(); } } showMessage(event) { this.setAttribute('count', this.getAttribute('count') + 1); } } 



Cada artículo recibió un contador independiente, actualizado automáticamente.

Tarea: implemente la conversión del contador a un número y utilícelo a través de un auto-getter;)

Por supuesto, son posibles opciones más avanzadas. Por ejemplo, si no tiene en cuenta las devoluciones de llamada y los eventos simples, utilizando el Proxy nativo y los mismos captadores y configuradores, puede implementar la funcionalidad de enlace inverso.

Agregue el archivo my-counter.js con una clase de este tipo

 export class MyCounter extends EventTarget { constructor() { super(); this.count = 0; } increment() { this.count++; this.dispatchEvent(new CustomEvent('countChanged', { detail: { count: this.count } })); } } 

Heredamos una clase de EventTarget para que otras clases puedan suscribirse a eventos lanzados por objetos de esta clase y definir una propiedad de conteo que almacenará el valor del contador.

Ahora agregue la instancia de esta clase como una propiedad estática para el componente.

 <script type="module"> import { MyWebComp } from "./my-webcomp.js"; import { MyCounter } from "./my-counter.js"; let counter = new MyCounter(); Object.defineProperty(MyWebComp.prototype, 'counter', { value: counter }); customElements.define('my-webcomp', MyWebComp); </script> 

Y en el código del componente, nos suscribiremos al cambio de valor del método de actualización de la etiqueta updateLabel () , al que agregamos la visualización del valor del contador compartido global. Y en el controlador de clics, una llamada directa al método incremental.

 export class MyWebComp extends HTMLElement { constructor() { super(); } connectedCallback() { let html = document.importNode(myWebCompTemplate.content, true); this.attachShadow({mode: 'open'}); this.shadowRoot.appendChild(html); this.updateLabel(); this.counter.addEventListener('countChanged', this.updateLabel.bind(this)); } updateLabel() { this.shadowRoot.querySelector('#helloLabel').textContent = 'Hello ' + this.getAttribute('greet-name') + ' ' + this.getAttribute('count') + ' ' + this.counter.count; } static get observedAttributes() { return ['count']; } attributeChangedCallback(name, oldValue, newValue) { if (name === 'count') { if (this.shadowRoot) { this.updateLabel(); } } } showMessage(event) { this.setAttribute('count', parseInt(this.getAttribute('count')) + 1); this.counter.increment(); } } 

A pesar de que cada elemento recibió dos contadores, cada uno de sus valores se incrementará de forma independiente, pero el valor del contador compartido será el mismo para ambos elementos, y los valores individuales estarán determinados por el número de clics solo en ellos.



Por lo tanto, obtuvimos enlace directo en enlace directo, y debido al uso de eventos, este valor es débil en la actualización. Por supuesto, nada impide que el incremento se implemente a través de eventos, al vincular el método increment () al lyser del evento:

 export class MyCounter extends EventTarget { constructor() { super(); this.count = 0; this.addEventListener('increment', this.increment.bind(this)); } increment() { this.count++; this.dispatchEvent(new CustomEvent('countChanged', { detail: { count: this.count } })); } } 

y reemplazando la llamada al método con un evento de lanzamiento:

 export class MyWebComp extends HTMLElement { ... showMessage(event) { this.setAttribute('count', parseInt(this.getAttribute('count')) + 1); this.counter.dispatchEvent(new CustomEvent('increment')); } } 

¿Qué cambia eso? Ahora, si durante el desarrollo se elimina o cambia el método increment () , se violará la exactitud de nuestro código, pero no habrá errores de interpretación, es decir la operabilidad se mantendrá. Esta característica se llama conectividad débil.

En el desarrollo, se necesitan enlaces directos y débiles, por lo general, es habitual implementar el enlace directo dentro de la lógica de un módulo de componentes y un acoplamiento débil entre diferentes módulos y componentes para proporcionar flexibilidad y extensibilidad de todo el sistema. La semántica HTML implica un alto nivel de flexibilidad, es decir. preferencia por una conectividad débil, pero la aplicación funcionará de manera más confiable si las conexiones dentro de los componentes son directas.

Podemos colgar manejadores a la antigua usanza y llamar a eventos en el objeto de documento global.

 export class MyWebComp extends HTMLElement { constructor() { super(); } connectedCallback() { let html = document.importNode(myWebCompTemplate.content, true); this.attachShadow({mode: 'open'}); this.shadowRoot.appendChild(html); this.updateLabel(); this.counter.addEventListener('countChanged', this.updateLabel.bind(this)); document.addEventListener('countChanged', this.updateLabel.bind(this)); } disconnectedCallback() { document.removeEventListener(new CustomEvent('increment')); document.removeEventListener(new CustomEvent('countChanged')); } ... showMessage(event) { this.setAttribute('count', parseInt(this.getAttribute('count')) + 1); this.counter.dispatchEvent(new CustomEvent('increment')); document.dispatchEvent(new CustomEvent('increment')); } } 

 export class MyCounter extends EventTarget { constructor() { super(); this.count = 0; this.addEventListener('increment', this.increment.bind(this)); document.addEventListener('increment', this.increment.bind(this)); } increment() { this.count++; this.dispatchEvent(new CustomEvent('countChanged', { detail: { count: this.count } })); document.dispatchEvent(new CustomEvent('countChanged', { detail: { count: this.count } })); } } 

El comportamiento no es diferente, pero ahora debemos pensar en eliminar el controlador de eventos del objeto global si el elemento mismo se elimina del árbol. Afortunadamente, los componentes web nos proporcionan no solo ganchos de diseño, sino también destrucción, evitando pérdidas de memoria.

También hay un punto más importante: los eventos de los elementos del árbol pueden interactuar entre sí a través de un mecanismo llamado "burbujeo".

Cuando el usuario hace clic en nuestro elemento, el evento, después de haber trabajado en el elemento en sí, comienza a llamarse al padre como círculos en el agua, luego al padre de este padre y así sucesivamente al elemento raíz.

En cualquier punto de esta cadena de llamadas, el evento puede ser interceptado y procesado.

Esto, por supuesto, no es exactamente el mismo evento, y sus derivados y contextos, aunque contendrán enlaces entre sí como, por ejemplo, en event.path , no coincidirán por completo.

Sin embargo, este mecanismo le permite asociar elementos secundarios con sus padres de tal manera que este enlace no viole los patrones de ingeniería, es decir. sin enlaces directos, pero solo debido a su propio comportamiento.

En la antigüedad, esto también causó muchos problemas, cuando los eventos de algunos elementos causaron "círculos" o ecos en algunas secciones del documento que no eran deseables para las reacciones.

Durante años, los desarrolladores web lucharon con la "propaganda" (propagación o aparición) de los eventos y los detuvieron y los convirtieron como pudieron, pero para su uso cómodo, como en el caso de las identificaciones, solo faltaba el aislamiento del árbol de la sombra.

La magia aquí es que los eventos no se saquean fuera del Árbol de las Sombras. Sin embargo, puede captar el evento lanzado por sus hijos en el componente host y enrollarlo en el árbol en forma de algún otro significado de evento, bueno, o simplemente procesarlo.

Para entender cómo funciona todo, envolvemos nuestros elementos webcomp en un contenedor como este:

 <my-webcont count=0> <my-webcomp id="myWebComp" greet-name="John" count=0 onclick="this.showMessage(event)"></my-webcomp> <my-webcomp id="myWebComp2" greet-name="Josh" count=0 onclick="this.showMessage(event)"></my-webcomp> </my-webcont> 

El contenedor tendrá este código:

 export class MyWebCont extends HTMLElement { constructor() { super(); } connectedCallback() { this.addEventListener('increment', this.updateCount.bind(this)); } updateCount(event) { this.setAttribute('count', parseInt(this.getAttribute('count')) + 1); } static get observedAttributes() { return ['count']; } attributeChangedCallback(name, oldValue, newValue) { if (name === 'count') { this.querySelectorAll('my-webcomp').forEach((el) => { el.setAttribute('count', newValue); }); } } } 

En connectedCallback (), colgaremos el controlador en el evento de incremento , que arrojará a los hijos. El controlador incrementará el contador del elemento, y una devolución de llamada para cambiar su valor pasará por todos los elementos secundarios e incrementará sus contadores, en los que cuelgan los controladores desarrollados previamente por nosotros.

El código de los elementos secundarios cambiará ligeramente, de hecho, todo lo que necesitamos es que el evento de incremento arroje el elemento en sí y no sus agregados, y lo haga con las burbujas: atributo verdadero .

 export class MyWebComp extends HTMLElement { ... showMessage(event) { this.setAttribute('count', parseInt(this.getAttribute('count')) + 1); this.counter.dispatchEvent(new CustomEvent('increment')); this.dispatchEvent(new CustomEvent('increment', { bubbles: true })); document.dispatchEvent(new CustomEvent('increment')); } } 



Ahora, a pesar de cierta aleatoriedad general, los primeros contadores de elementos siempre mostrarán el valor del padre. En conjunto, lo que hemos hecho hoy aquí ciertamente no es un ejemplo a seguir, sino solo una descripción general de las posibilidades y técnicas mediante las cuales puede organizar una interacción verdaderamente modular entre componentes, evitando un mínimo de herramientas.

Puede encontrar el código terminado para la referencia en el mismo repositorio, en el brunch de eventos .

Source: https://habr.com/ru/post/461153/


All Articles