Composants Web sans Shadow DOM

Bonjour, Habr!

Récemment, j'ai remarqué un certain nombre d'articles critiquant toutes sortes de composants Web. Parfois, cette critique est très dure et sent même la haine. À mon avis, le principal problème ici est le manque de pratique établie de travailler avec ce groupe de normes parmi la communauté des développeurs. De nombreux modèles familiers ne s'intègrent pas toujours de manière organique dans les projets impliquant des éléments personnalisés et le Shadow DOM , beaucoup de choses doivent être vues sous un nouvel angle, et tout le monde n'aime pas ça. Je travaille avec succès avec des composants Web depuis plusieurs années et j'ai même développé ma propre bibliothèquebasé sur eux, donc je pense que cette situation n'est pas très juste. Je vais essayer, au moins partiellement, de le réparer, dans la mesure de ma modeste force. J'ai décidé de faire une série de publications compactes, dans chacune desquelles je compte aborder l'un des aspects les plus fréquents de la critique, ainsi que de démontrer un certain nombre de techniques techniques qui peuvent être intéressantes pour ceux qui n'ont pas encore décidé de quel côté de la barricade il devrait être. Aujourd'hui, je voudrais parler de la façon de créer des composants sans DOM Shadow.

Pourquoi?


L'idée principale que je veux transmettre cette fois est que les composants Web et le DOM Shadow ne sont pas la même chose. Lorsque vous utilisez le Shadow DOM, vous obtenez deux avantages principaux:

  • Une section isolée du document dans laquelle vos styles se sentent à l'abri des influences extérieures et des «fuites»
  • Un mécanisme de composition qui vous permet de diviser le document en quelle est la structure du composant lui-même et son contenu (descendants de l'élément DOM dans l'arborescence)

Cependant, cette mécanique entraîne également certains coûts pour créer et styliser un espace isolé, ce qui est assez naturel. Dans certains cas (grandes listes, cellules de tableau avec données, etc.), je souhaite éviter ces coûts pour des raisons d'optimisation des performances. Maintenant, nous allons le réparer:

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 vous connaissez déjà la norme Custom Elements, vous remarquerez immédiatement ce qui se passe: au lieu d'appeler la méthode attachShadowdans le constructeur du composant, nous avons créé un DocumentFragment dans lequel nous avons cloné un modèle pré-préparé. À ce stade, le composant n'est pas rendu par le navigateur et il peut être modifié en toute sécurité, par exemple, lier / insérer des données.

La prochaine étape importante est liée au cycle de vie des éléments personnalisés. Les composants ne sont ajoutés au document général qu'après que le constructeur a pleinement fonctionné et jusqu'à ce moment, la partie de l'API DOM chargée de travailler avec les parents ou les descendants de l'élément, ainsi qu'avec les attributs, ne sera pas disponible. Par conséquent, pour ajouter directement du contenu à notre composant, nous utilisons connectedCallback.

Lors de la création du modèle, nous avons utilisé la méthode pour plus de simplicité innerHTML. Cette opération n'est effectuée qu'une seule fois, lors de la création de l'élément «template», elle n'est pas répétée à chaque fois qu'une instance de notre composant est créée. Cependant, ce point peut également être encore optimisé en créant impérativement des motifs.

Au total, en utilisant une balise personnalisée dans notre balisage shadowless-component, nous obtenons le résultat suivant dans le navigateur:

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

Depuis, après avoir éliminé ShadowRoot, nous avons perdu l'isolement des styles, nous avons ajouté des styles à notre modèle en utilisant l'attribut. Dans ce cas, ils ont la priorité, cela résout partiellement le problème et peut être utilisé dans des endroits importants. Pour tous les autres cas, le style classique est disponible via une feuille de style commune et les balises personnalisées agissent comme des sélecteurs pratiques.

affichage: contenu


Les composants Web sont les nœuds complets de votre DOM. Cela signifie, en plus du fait que toutes les méthodes standard des éléments DOM sont à votre disposition, et que votre composant est toujours une sorte de conteneur. Autrement dit, si vous souhaitez ajouter une structure arbitraire d'éléments au DOM à l'aide du composant Web, ils seront tous des descendants de votre composant, ce qui n'est pas toujours pratique. Dans de tels cas, vous pouvez utiliser la nouvelle règle CSS - display: contents . Prise en charge du navigateur: caniuse.com/#feat=css-display-contents

Par défaut, tous les composants ont la propriété display: inline .

Un peu de mal


Mais que se passe-t-il si nous ne voulons pas du tout de conteneurs supplémentaires et de balises personnalisées? Donnez du HTML pur!

D'ACCORD:

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

En conséquence, nous obtenons ceci:

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

Tous les événements et liaisons continuent de fonctionner et sont contrôlés par notre composant, qui n'existe désormais qu'en mémoire. Dans ce cas, vous devrez prendre des précautions supplémentaires pour vous désabonner et tout autre nettoyage des ordures au moment où vous souhaitez supprimer complètement le composant.

Composants CSS


Selon la norme, les balises personnalisées doivent être nommées avec l'ajout obligatoire d'un caractère "-". Si vous utilisez votre balise dans le balisage, mais en même temps ne créez aucun composant dans JS et ajoutez son constructeur au registre des composants, le navigateur considère votre balise comme un «élément inconnu» ( HTMLUnknownElement ). Par défaut, ces éléments ont un comportement similaire à la balise span. Cela peut être utilisé si vous avez besoin de créer un simple composant stupide avec une structure simple pour laquelle les expressions CSS :: before , :: after et attr () sont suffisantes . Exemple:

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

Utilisation dans le balisage:

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

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


All Articles