Quando se trata de componentes da Web, eles costumam dizer: “O que você deseja sem estruturas? Está tudo pronto lá. De fato, existem estruturas criadas com base nas implementações dos padrões incluídos no grupo de componentes da web. Existem até relativamente bons como o
X-Tag . Mas hoje ainda entenderemos o quão simples, elegante e poderosa a API do navegador se tornou para resolver tarefas diárias de desenvolvimento, incluindo a organização da interação de componentes entre si e com outros objetos do contexto da execução do navegador, e você sempre pode usar estruturas com componentes da Web, mesmo aqueles que foram desenvolvidos de acordo com os padrões, inclusive através dos mecanismos que analisaremos hoje, pelo menos conforme declarado
no site .
Decisões como um reagente nos ensinaram que deveria haver um lado de grande volume de dados e os componentes assinados para suas alterações, e nos componentes da web eles também estão tentando discernir a ausência de um subsistema, o que implica de fato uma ligação inversa inconsciente e que já existe nos componentes da web.
Cada elemento possui atributos do valor que podemos alterar. E se você listar os nomes no gancho
observadoAttributes , quando eles mudarem,
attributeChangedCallback () será automaticamente chamado, para que possamos determinar o comportamento do componente quando o atributo mudar. Usando a magia getter, é fácil fazer a ligação reversa de maneira semelhante.
Já esboçamos algum projeto na
primeira parte e hoje continuaremos a cortá-lo ainda mais.
Vale mencionar imediatamente uma limitação, pelo menos na implementação atual, que o valor do atributo pode ser apenas um valor primitivo especificado por um literal e redutível a uma string, mas, em geral, é possível transferir "objecs" dessa maneira usando aspas simples de fora e duplo para definir valores e campos do projeto.
<my-component my='{"foo": "bar"}'></my-component>
Para usar esse valor no código, você pode implementar um getter
mágico automático que chamará
JSON.parse () nele .
Mas, por enquanto, o valor numérico do contador é suficiente.
Adicionamos um novo atributo ao nosso elemento, especificamos como observado, clique no manipulador de cliques para incrementar esse contador e adicionamos a atualização do valor exibido por link direto ao gancho de alterações, cuja lógica é implementada em um método
updateLabel () reutilizável 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 item recebeu um contador independente e atualizado automaticamente.
Trabalho de casa: implemente a conversão do contador em um número e use-o através de um auto-getter;)
Obviamente, são possíveis opções mais avançadas. Por exemplo, se você não considerar retornos de chamada e eventos simples, usando o Proxy nativo e os mesmos getters e setters, poderá implementar a funcionalidade da ligação reversa.
Adicione
o arquivo
my-counter.js a uma classe desse tipo
export class MyCounter extends EventTarget { constructor() { super(); this.count = 0; } increment() { this.count++; this.dispatchEvent(new CustomEvent('countChanged', { detail: { count: this.count } })); } }
Herdamos uma classe de
EventTarget para que outras classes possam se inscrever em eventos lançados por objetos dessa classe e definir uma propriedade count que armazenará o valor do contador.
Agora adicione a instância desta classe como uma propriedade estática para o 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>
E no código do componente, assinaremos o método
change update do rótulo
updateLabel () , ao qual adicionamos a exibição do valor no contador compartilhado global. E no manipulador de cliques, uma chamada direta para o 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(); } }
Apesar de cada elemento receber dois contadores, cada um dos seus valores será incrementado independentemente, mas o valor do contador compartilhado será o mesmo para os dois elementos, e os valores individuais serão determinados pelo número de cliques apenas neles.

Assim, obtivemos ligação direta na ligação direta e, devido ao uso de eventos, esse valor é fraco na atualização. Obviamente, nada impede que o incremento seja implementado por meio de eventos, vinculando o método
increment () ao lisador do 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 } })); } }
e substituindo a chamada do método por uma ocorrência de evento:
export class MyWebComp extends HTMLElement { ... showMessage(event) { this.setAttribute('count', parseInt(this.getAttribute('count')) + 1); this.counter.dispatchEvent(new CustomEvent('increment')); } }
O que isso muda? Agora, se durante o desenvolvimento o método
increment () for removido ou alterado, a correção do nosso código será violada, mas não haverá erros de intérprete, ou seja, operacional permanecerá. Essa característica é chamada de conectividade fraca.
No desenvolvimento, são necessárias ligações diretas e fracas, geralmente é habitual implementar a ligação direta dentro da lógica de um módulo de componente e uma ligação fraca entre diferentes módulos e componentes, a fim de fornecer flexibilidade e extensibilidade a todo o sistema. A semântica HTML implica um alto nível de flexibilidade, ou seja, preferência por conectividade fraca, mas o aplicativo funcionará de maneira mais confiável se as conexões dentro dos componentes forem diretas.
Podemos desligar manipuladores à moda antiga e chamar eventos no 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 } })); } }
O comportamento não é diferente, mas agora precisamos pensar em remover o manipulador de eventos do objeto global se o próprio elemento for removido da árvore. Felizmente, os componentes da Web nos fornecem não apenas ganchos de design, mas também destruição, evitando vazamentos de memória.
Há também um ponto mais importante: os eventos dos elementos da árvore podem interagir entre si por meio de um mecanismo chamado "bolhas".
Quando o usuário clica em nosso elemento, o evento, tendo trabalhado no próprio elemento, começa a ser chamado no pai como círculos na água, depois no pai desse pai e assim por diante no elemento raiz.
A qualquer momento dessa cadeia de chamadas, o evento pode ser interceptado e processado.
Obviamente, esse não é o mesmo evento, e seus derivados e contextos, embora eles contenham links entre si, como por exemplo em
event.path , não coincidem completamente.
No entanto, esse mecanismo permite associar elementos filhos aos pais, de modo que esse link não viole os padrões de engenharia, ou seja, sem links diretos, mas apenas devido ao seu próprio comportamento.
Nos tempos antigos, isso também gerou muitos problemas, quando os eventos de alguns elementos causaram "círculos" ou ecos em algumas seções do documento que eram indesejáveis para reações.
Por anos, os desenvolvedores da web lutaram com a "propaganda" (divulgação ou divulgação) dos eventos e os detiveram e os transformaram como puderam, mas para seu uso confortável, como no caso dos IDs, faltava apenas o isolamento da árvore das sombras.
A mágica aqui é que os eventos não saquearam fora da Árvore das Sombras. No entanto, você pode capturar o evento lançado por seus filhos no componente host e enrolá-lo na árvore na forma de algum outro significado de evento, ou simplesmente processá-lo.
Para entender como tudo funciona, envolvemos nossos elementos de
webcomp em um contêiner 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>
O contêiner terá 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); }); } } }
Em
connectedCallback (), travaremos o manipulador no evento de
incremento , que lançará os filhos. O manipulador incrementará o contador do próprio elemento, e um retorno de chamada para alterar seu valor passará por todos os elementos filhos e incrementará seus contadores, nos quais os manipuladores desenvolvidos anteriormente por nós permanecem.
O código dos elementos filhos mudará um pouco; na verdade, tudo o que precisamos é que o evento
incremento jogue fora o elemento em si e não seus agregados, e faça isso com o atributo
bubbles: true .
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')); } }

Agora, apesar de alguma aleatoriedade geral, os contadores do primeiro elemento sempre mostrarão o valor do pai. No conjunto, o que fizemos aqui hoje certamente não é um exemplo a seguir, mas apenas uma visão geral das possibilidades e técnicas pelas quais você pode organizar uma interação verdadeiramente modular entre componentes, evitando o mínimo de ferramentas.
Você pode encontrar o código finalizado para a referência no mesmo repositório, no
brunch de eventos .