Componentes web sin Shadow DOM

Hola Habr!

Recientemente, noté una serie de artículos que criticaban todo tipo de componentes web. A veces, esta crítica es muy dura e incluso huele a odio. En mi opinión, el principal problema aquí es la falta de práctica establecida de trabajar con este grupo de estándares entre la comunidad de desarrolladores. Muchos modelos familiares no siempre encajan orgánicamente en proyectos que involucran elementos personalizados y Shadow DOM , muchas cosas tienen que verse desde un nuevo ángulo, y no a todos les gusta. He trabajado con éxito con componentes web durante varios años e incluso desarrollé mi propia bibliotecabasado en ellos, así que creo que esta situación no es muy justa. Intentaré, al menos parcialmente, arreglarlo, en la medida de mi modesta fuerza. Decidí hacer una serie de publicaciones compactas, en cada una de las cuales planeo tocar uno de los aspectos frecuentes de la crítica, así como demostrar una serie de técnicas técnicas que pueden ser interesantes para aquellos que aún no han decidido en qué lado de la barricada debería estar. Hoy me gustaría hablar sobre cómo crear componentes sin un DOM de sombra.

¿Para qué?


La idea principal que quiero transmitir esta vez es que los componentes web y el DOM DOM no son lo mismo. Al usar Shadow DOM, obtienes dos beneficios principales:

  • Una sección aislada del documento en la que sus estilos se sienten a salvo de influencias externas y "filtraciones"
  • Un mecanismo de composición que le permite dividir el documento en cuál es la estructura del componente en sí y sus contenidos (descendientes del elemento DOM en el árbol)

Sin embargo, esta mecánica también conlleva algunos costos para crear y diseñar un espacio aislado, lo cual es bastante natural. En algunos casos (listas grandes, celdas de tabla con datos, etc.), quiero evitar estos costos por razones de optimización del rendimiento. Ahora lo arreglaremos:

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);

Si ya está familiarizado con el estándar Custom Elements, notará de inmediato lo que está sucediendo: en lugar de llamar al método attachShadowen el constructor de componentes, creamos un DocumentFragment en el que clonamos una plantilla preparada previamente. En esta etapa, el navegador no procesa el componente y puede modificarse de manera relativamente segura, por ejemplo, vincular / insertar datos.

El siguiente paso importante está relacionado con el ciclo de vida de Elementos personalizados. Los componentes se agregan al documento general solo después de que el constructor haya trabajado completamente y hasta ese momento, esa parte de la API DOM que es responsable de trabajar con los padres o descendientes del elemento, así como con los atributos, no estará disponible. Por lo tanto, para agregar contenido directamente a nuestro componente, utilizamos connectedCallback.

Al crear la plantilla, utilizamos el método por simplicidad innerHTML. Esta operación se realiza solo una vez, al crear el elemento "plantilla", no se repite cada vez que se crea una instancia de nuestro componente. Sin embargo, este punto también puede optimizarse aún más creando patrones imperativamente.

En total, usando una etiqueta personalizada en nuestro marcado shadowless-component, obtenemos el siguiente resultado en el 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>

Dado que, al deshacernos de ShadowRoot, perdimos el aislamiento de los estilos, agregamos estilos a nuestra plantilla utilizando el atributo En este caso, tienen prioridad, esto resuelve parcialmente el problema y puede usarse en lugares importantes. Para todos los demás casos, el estilo clásico está disponible a través de una hoja de estilo común, y las etiquetas personalizadas actúan como selectores convenientes.

display: contenido


Los componentes web son los nodos completos de su DOM. Esto significa, además del hecho de que todos los métodos estándar de elementos DOM están disponibles para usted, y que su componente siempre es una especie de contenedor. Es decir, si desea agregar una estructura arbitraria de elementos al DOM utilizando el componente web, todos serán descendientes de su componente, lo que no siempre es conveniente. En tales casos, puede usar la nueva regla CSS: display: contenidos . Soporte del navegador: caniuse.com/#feat=css-display-contents

Por defecto, todos los componentes tienen la propiedad display: inline .

Un poco de travesura


Pero, ¿qué pasa si no queremos ningún contenedor adicional y etiquetas personalizadas? ¡Dale HTML puro!

OKAY:

  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, obtenemos esto:

<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 los eventos y enlaces continúan funcionando y están controlados por nuestro componente, que ahora solo existe en la memoria. En este caso, tendrá que tener cuidado adicional de cancelar la suscripción y otra limpieza de la basura en el momento en que desee eliminar el componente por completo.

Componentes CSS


Según el estándar, las etiquetas personalizadas deben nombrarse con la adición obligatoria de un carácter "-". Si usa su etiqueta en el marcado, pero al mismo tiempo no crea ningún componente en JS y agrega su constructor al registro de componentes, el navegador considera que su etiqueta es un "elemento desconocido" ( HTMLUnknownElement ). Por defecto, estos elementos tienen un comportamiento similar al de la etiqueta span. Esto se puede usar si necesita crear un componente tonto simple con una estructura simple para la cual las reglas CSS :: before , :: after y attr () sean suficientes . Ejemplo:

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

Uso en marcado:

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

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


All Articles