Webkomponenten ohne Shadow DOM

Hallo Habr!

Kürzlich bemerkte ich eine Reihe von Artikeln, in denen alle Arten von Webkomponenten kritisiert wurden. Manchmal ist diese Kritik sehr hart und riecht sogar nach Hass. Meiner Meinung nach ist das Hauptproblem hier das Fehlen einer etablierten Praxis, mit dieser Gruppe von Standards in der Entwicklergemeinschaft zu arbeiten. Viele bekannte Modelle passen nicht immer organisch in Projekte mit benutzerdefinierten Elementen und Shadow DOM . Viele Dinge müssen aus einem neuen Blickwinkel betrachtet werden, und nicht jeder mag es. Ich arbeite seit mehreren Jahren erfolgreich mit Webkomponenten und entwickle sogar meine eigene Bibliothekbasierend auf ihnen, daher denke ich, dass diese Situation nicht sehr fair ist. Ich werde zumindest teilweise versuchen, es im Ausmaß meiner bescheidenen Stärke zu beheben. Ich beschloss, eine Reihe kompakter Veröffentlichungen zu verfassen, in denen ich jeweils einen der häufigsten Aspekte der Kritik ansprechen und eine Reihe technischer Techniken demonstrieren möchte, die für diejenigen interessant sein könnten, die sich noch nicht entschieden haben, auf welcher Seite der Barrikade er sich befinden soll. Heute möchte ich darüber sprechen, wie Komponenten ohne Shadow DOM erstellt werden.

Wozu?


Die Hauptidee, die ich dieses Mal vermitteln möchte, ist, dass Webkomponenten und das Shadow DOM nicht dasselbe sind. Bei Verwendung des Shadow DOM erhalten Sie zwei Hauptvorteile:

  • Ein isolierter Abschnitt des Dokuments, in dem sich Ihre Stile vor äußeren Einflüssen und „Lecks“ sicher fühlen.
  • Ein Kompositionsmechanismus, mit dem Sie das Dokument in die Struktur der Komponente selbst und ihren Inhalt (Nachkommen des DOM-Elements im Baum) unterteilen können.

Diese Mechanik verursacht jedoch auch einige Kosten für die Erstellung und Gestaltung eines isolierten Raums, was ganz natürlich ist. In einigen Fällen (große Listen, Tabellenzellen mit Daten usw.) möchte ich diese Kosten aus Gründen der Leistungsoptimierung vermeiden. Jetzt werden wir es beheben:

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

Wenn Sie bereits mit dem Standard für benutzerdefinierte Elemente vertraut sind, werden Sie sofort feststellen, was passiert: Anstatt die Methode attachShadowim Komponentenkonstruktor aufzurufen , haben wir ein DocumentFragment erstellt, in das wir eine vorbereitete Vorlage geklont haben. Zu diesem Zeitpunkt wird die Komponente nicht vom Browser gerendert und kann relativ sicher geändert werden, z. B. zum Binden / Einfügen von Daten.

Der nächste wichtige Schritt bezieht sich auf den Lebenszyklus von benutzerdefinierten Elementen. Komponenten werden dem allgemeinen Dokument erst hinzugefügt, nachdem der Konstruktor vollständig gearbeitet hat. Bis zu diesem Zeitpunkt ist der Teil der DOM-API, der für die Arbeit mit den übergeordneten oder untergeordneten Elementen des Elements sowie mit Attributen verantwortlich ist, nicht verfügbar. Daher verwenden wir, um unserer Komponente direkt Inhalte hinzuzufügen connectedCallback.

Bei der Erstellung der Vorlage haben wir die Methode der Einfachheit halber verwendet innerHTML. Dieser Vorgang wird nur einmal ausgeführt. Wenn Sie das Element "Vorlage" erstellen, wird er nicht jedes Mal wiederholt, wenn eine Instanz unserer Komponente erstellt wird. Dieser Punkt kann jedoch auch weiter optimiert werden, indem unbedingt Muster erstellt werden müssen.

Insgesamt erhalten shadowless-componentwir mit einem benutzerdefinierten Tag in unserem Markup das folgende Ergebnis im Browser:

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

Nachdem wir ShadowRoot entfernt haben und die Isolation der Stile verloren haben, haben wir unserer Vorlage mithilfe des Attributs Stile hinzugefügt. In diesem Fall haben sie Priorität, dies löst das Problem teilweise und kann an wichtigen Stellen eingesetzt werden. In allen anderen Fällen ist das klassische Styling über ein gemeinsames Stylesheet verfügbar, und benutzerdefinierte Tags dienen als praktische Auswahl.

Anzeige: Inhalt


Webkomponenten sind die vollständigen Knoten Ihres DOM. Dies bedeutet zusätzlich zu der Tatsache, dass Ihnen alle Standardmethoden für DOM-Elemente zur Verfügung stehen und dass Ihre Komponente immer eine Art Container ist. Wenn Sie dem DOM mithilfe der Webkomponente eine beliebige Struktur von Elementen hinzufügen möchten, sind alle Nachkommen Ihrer Komponente, was nicht immer praktisch ist. In solchen Fällen können Sie die neue CSS-Regel verwenden - Anzeige: Inhalt . Browserunterstützung : caniuse.com/#feat=css-display-contents

Standardmäßig verfügen alle Komponenten über die Eigenschaft display: inline .

Ein bisschen Unheil


Aber was ist, wenn wir überhaupt keine zusätzlichen Container und benutzerdefinierten Tags möchten? Gib reines HTML!

OK:

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

Als Ergebnis erhalten wir Folgendes:

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

Alle Ereignisse und Bindungen funktionieren weiterhin und werden von unserer Komponente gesteuert, die nur noch im Speicher vorhanden ist. In diesem Fall müssen Sie sich zusätzlich um das Abbestellen und andere Fremdkörper kümmern, sobald Sie die Komponente vollständig entfernen möchten.

CSS-Komponenten


Benutzerdefinierte Tags müssen standardmäßig mit dem obligatorischen Zusatz "-" gekennzeichnet werden. Wenn Sie Ihr Tag im Markup verwenden, aber gleichzeitig keine Komponente in JS erstellen und ihren Konstruktor zur Komponentenregistrierung hinzufügen, betrachtet der Browser Ihr Tag als „unbekanntes Element“ ( HTMLUnknownElement ). Standardmäßig verhalten sich diese Elemente ähnlich wie das span-Tag. Dies kann verwendet werden, wenn Sie eine einfache dumme Komponente mit einer einfachen Struktur erstellen müssen, für die CSS-Regeln :: before , :: after und attr () ausreichen . Beispiel:

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

Verwendung im Markup:

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

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


All Articles