Componentes da Web sem Shadow DOM

Olá Habr!

Recentemente, notei vários artigos criticando todos os tipos de componentes da web. Às vezes, essa crítica é muito dura e até cheira a ódio. Na minha opinião, o principal problema aqui é a falta de prática estabelecida de trabalhar com esse grupo de padrões entre a comunidade de desenvolvedores. Muitos modelos familiares nem sempre se encaixam organicamente em projetos que envolvem elementos personalizados e sombra DOM , muitas coisas precisam ser vistas de um novo ângulo e nem todo mundo gosta. Trabalho com componentes da web há vários anos e até desenvolvo minha própria bibliotecacom base neles, então acho que essa situação não é muito justa. Vou tentar, pelo menos parcialmente, consertá-lo, na medida da minha força modesta. Decidi fazer uma série de publicações compactas, em cada uma das quais pretendo abordar um dos aspectos frequentes da crítica, bem como demonstrar uma série de técnicas técnicas que podem ser interessantes para aqueles que ainda não decidiram em que lado da barricada ele deve estar. Hoje eu gostaria de falar sobre como criar componentes sem um Shadow DOM.

Pelo que?


A idéia principal que quero transmitir dessa vez é que os componentes da Web e o DOM da sombra não são a mesma coisa. Ao usar o Shadow DOM, você obtém dois benefícios principais:

  • Uma seção isolada do documento em que seus estilos se sentem seguros contra influências externas e "vazamentos"
  • Um mecanismo de composição que permite dividir o documento em qual é a estrutura do próprio componente e seu conteúdo (descendentes do elemento DOM na árvore)

No entanto, essa mecânica também acarreta alguns custos para criar e estilizar um espaço isolado, o que é bastante natural. Em alguns casos (listas grandes, células de tabela com dados etc.), desejo evitar esses custos por motivos de otimização de desempenho. Agora vamos corrigi-lo:

const MY_CSS = {
  title: 'color: #00f; font-size: 2em',
  item: 'color: #f00; font-size: 1.2em',
};

const DATA = [
  {text: 'Text 1'},
  {text: 'Text 2'},
  {text: 'Text 3'},
];

let template = document.createElement('template');
template.innerHTML = /*html*/ `
<div style="${MY_CSS_.title}">List items:</div>
<div class="my-list">
  ${DATA.map(item => /*html*/ `<div style="${MY_CSS.item}">${item.text}</div>`).join('')}
</div>
`;

class ShadowlessComponent extends HTMLElement {
  constructor() {
    super();
    this._contents = new DocumentFragment();
    this._contents.appendChild(template.content.cloneNode(true));
  }
  connectedCallback() {
    this.appendChild(this._contents);
  }
}

window.customElements.define('shadowless-component', ShadowlessComponent);

Se você já conhece o padrão Custom Elements, notará imediatamente o que está acontecendo: em vez de chamar o método attachShadowno construtor de componentes, criamos um DocumentFragment no qual clonamos um modelo pré-preparado. Nesse estágio, o componente não é renderizado pelo navegador e pode ser modificado com segurança, por exemplo, vincular / inserir dados.

A próxima etapa importante está relacionada ao ciclo de vida dos elementos personalizados. Os componentes são adicionados ao documento geral somente depois que o construtor tiver trabalhado totalmente e até esse momento, a parte da API do DOM responsável por trabalhar com os pais ou descendentes do elemento, bem como com os atributos, não estará disponível. Portanto, para adicionar conteúdo diretamente ao nosso componente, usamos connectedCallback.

Ao criar o modelo, usamos o método para simplificar innerHTML. Esta operação é executada apenas uma vez, ao criar o elemento "template", não é repetida toda vez que uma instância do nosso componente é criada. No entanto, esse ponto também pode ser otimizado ainda mais criando padrões imperativamente.

No total, usando uma tag personalizada em nossa marcação shadowless-component, obtemos o seguinte resultado no navegador:

<shadowless-component>
  <div id="caption" style="color: #00f; font-size: 2em">List items:</div>
    <div class="my-list">
      <div style="color: #f00; font-size: 1.2em">Text 1</div>
      <div style="color: #f00; font-size: 1.2em">Text 2</div>
      <div style="color: #f00; font-size: 1.2em">Text 3</div>
    </div>
</shadowless-component>

Como, ao nos livrarmos do ShadowRoot, perdemos o isolamento de estilos, adicionamos estilos ao nosso modelo usando o atributo Nesse caso, eles têm prioridade, isso resolve parcialmente o problema e pode ser usado em lugares importantes. Para todos os outros casos, o estilo clássico está disponível em uma folha de estilos comum e as tags personalizadas atuam como seletores convenientes.

display: conteúdo


Componentes da Web são os nós completos do seu DOM. Isso significa, além do fato de que todos os métodos padrão de elementos DOM estão disponíveis para você, e que seu componente é sempre um tipo de contêiner. Ou seja, se você deseja adicionar uma estrutura arbitrária de elementos ao DOM usando o componente da Web, todos eles serão descendentes do seu componente, o que nem sempre é conveniente. Nesses casos, você pode usar a nova regra CSS - display: contents . Suporte ao navegador: caniuse.com/#feat=css-display-contents

Por padrão, todos os componentes têm a propriedade display: inline .

Um pouco de travessura


Mas e se não quisermos recipientes extras e tags personalizadas? Dê HTML puro!

ESTÁ BEM:

  constructor() {
    super();
    this._contents = new DocumentFragment();
    this._contents.appendChild(template.content.cloneNode(true));
    this._titleEl = this._contents.querySelector('#caption');
    window.setInterval(() => {
      this._titleEl.textContent = Date.now();
    }, 1000);
  }
  connectedCallback() {
    this.parentNode.prepend(this._contents, this);
    this.remove();
  }

Como resultado, obtemos o seguinte:

<div id="caption" style="color: #00f; font-size: 2em">1581075598392</div>
<div class="my-list">
  <div style="color: #f00; font-size: 1.2em">Text 1</div>
  <div style="color: #f00; font-size: 1.2em">Text 2</div>
  <div style="color: #f00; font-size: 1.2em">Text 3</div>
</div>

Todos os eventos e ligações continuam funcionando e são controlados por nosso componente, que agora existe apenas na memória. Nesse caso, você terá que cuidar adicionalmente de cancelamentos de assinatura e outras limpezas do lixo no momento em que deseja remover o componente completamente.

Componentes CSS


De acordo com o padrão, as tags personalizadas devem ser nomeadas com a adição obrigatória de um caractere "-". Se você usar sua marca na marcação, mas ao mesmo tempo não criar nenhum componente em JS e adicionar seu construtor ao registro do componente, o navegador considerará sua marca como um "elemento desconhecido" ( HTMLUnknownElement ). Por padrão, esses elementos têm comportamento semelhante à tag span. Isso pode ser usado se você precisar criar um componente idiota simples com uma estrutura simples para a qual as regras CSS rules :: before , :: after e attr () sejam suficientes . Exemplo:

  my-container {
    display: block;
    padding: 10px;
    border: 1px solid currentColor;
  }
  my-container::before {
    content: attr(caption);
    margin-bottom: .6em;
  }

Use na marcação:

<my-container caption=""></my-container>

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


All Articles