Werte dein Frontend auf mit React-Web-Components

6 August 2024

Es gibt viele Möglichkeiten, React in deine Anwendungen zu integrieren. Eine davon haben wir bereits vor langer Zeit näher beleuchtet. Als wir unser neuestes Projekt begonnen haben, haben wir uns Zeit genommen und überlegt, wie wir es diesmal besser machen können. Natürlich hätte auch der frühere Ansatz funktioniert, aber es gab ein paar Umstände bei diesem Projekt, die uns davon abhielten.

Der Großteil unserer Seite wurde dieses Mal im Backend über Django gerendert. Das bedeutete, dass unser Frontend keine vollwertige, eigenständige Single-Page-Application sein würde, sondern stattdessen aus kleinen, unabhängigen Komponenten auf der Seite bestehen würde, jeder mit eigener Logik und Datenverarbeitung.

Die Grundidee

Wir wollten unsere Components so definieren, dass die Verwendung so einfach ist wie der Aufruf eines nativen HTML-Tags, zum Beispiel:

<my-component school-id="1"/>

Als Erstes sollten wir die beiden Komponenten-Arten definieren, und aufzeigen, wie sie sich unterscheiden.

React-Components (React-Komponenten)

React-Components sind unabhängige und wiederverwendbare Codestücke, man kann sie sich wie React-Funktionen vorstellen. Sie stehen idealerweise für sich und geben HTML zurück. Wie der Name schon sagt, sind sie spezifisch für React.

Hier ist ein einfaches Beispiel für eine React-Komponente

import React from "react"

const MyComponent = () => {
   return <div>Hello World</div>
}

export default MyComponent

Natürlich kannst du eine React-Komponente auch in eine HTML-Seite laden, zum Beispiel mit

import { createRoot } from 'react-dom/client';

const domNode = document.getElementById('container');
const root = createRoot(domNode);
root.render(<MyComponent />);

Das funktioniert gut, ist aber nicht sehr deklarativ und nicht ganz, was wir uns vorgestellt haben.

Web-Components (Web Komponenten)

Bei Web-Components handelt es sich um eine Sammlung verschiedener Technologien, mit denen wiederverwendbare benutzerdefinierte Elemente erstellt werden können. Diese werden von Browsern nativ unterstützt und ihre Funktionalität ist vom Rest des Codes abgekapselt.

Im Gegensatz zu React-Components sind sie nicht auf ein Framework beschränkt. Die Standard-API ist komplex, systemnah und etwas imperativ. Sie setzen sich aus drei Technologien zusammen: benutzerdefinierte Elemente, Shadow-DOM und HTML-Vorlagen. Ich werde hier nicht weiter ins Detail gehen, aber kurz gefasst:

Benutzerdefinierte Elemente sind HTML-Elemente, deren Verhalten vom Webentwickler definiert wird, sie erweitern die Menge der im Browser verfügbaren Elemente. Sie haben eine gute Browserunterstützung auf Desktop und Handy.

class MyComponent extends HTMLElement {
   static observedAttributes = ["color", "size"];

   constructor() {
       // Always call super first in constructor
       super();
   }

   connectedCallback() {
       console.log("Custom element added to page.");
   }
}

customElements.define("my-component", MyComponent);

Das Shadow-DOM ist ein versteckter DOM-Baum, der an die Web-Component angehängt ist und ihre Logik kapselt, sodass sie der Logik der restlichen Seite nicht in die Quere kommt. Das Shadow-DOM wird von den meisten gängigen Browsern unterstützt.

HTML-Vorlagen sind Elemente, mit denen eine flexible Vorlage erstellt werden kann, die dann zum Auffüllen des Shadow-DOM einer Web-Component verwendet wird. Genau wie bei den anderen beiden Teilen ist die Browserunterstützung gegeben.

<template id="my-template">
   <p>My paragraph</p>
</template>

let template = document.getElementById("my-template");
let templateContent = template.content;
document.body.appendChild(templateContent);

Die Komplexität von Web-Components kommt vornehmlich vom Shadow-DOM und den Templates, zum Glück werden wir in unserem Ansatz nur benutzerdefinierte Elemente verwenden.

Was wir uns fragen?

Wenn du Web-Components und React googelst, erhältst du ungefähr folgende Beschreibung:

"Web-Components sind Browsernativ und können daher mit jedem JavaScript-Framework und jeder Bibliothek verwendet werden. React-Components hingegen sind spezifisch für die React-Bibliothek. React ist eine JavaScript-Bibliothek zur Erstellung von Benutzeroberflächen und verwendet ein virtuelles DOM, um den eigentlichen DOM effizient zu aktualisieren."

Die Frage ist also: Können wir React-Components als benutzerdefinierte Elemente verwenden?

Wenn wir uns die Definitionen ansehen, die wir zuvor gemacht haben, sind sie ähnlich genug, dass dies möglich sein sollte.

Aber warum sollten wir?

Nehmen wir uns einen Moment Zeit, um nach dem Warum zu fragen. Man kann Web-Componets auch ohne ein Framework erstellen, warum sich also die Mühe machen, React zu verwenden?

Wie bereits erwähnt, ist die Standard-API für Web-Components komplex und systemnah. Man kann Frameworks wie Lit verwenden, um dies zu umgehen, aber das würde die vorhandenen Vorteile nicht ausnutzen.

Außerdem müsste die Datenanbindung und die Zustandsverwaltung selbst implementiert werden, und wir erhalten standardmäßig keine Reaktivität. Das Angebot an Bibliotheken für Web-Components ist im Vergleich zu React noch klein und es gibt nicht sehr viele UI-Kits.

Allerdings ist die Browserunterstützung für Web Components in den letzten Jahren ziemlich gut geworden, und wir wollten sie ausprobieren.

React vereinfacht einiges, da es einen unidirektionalen Datenfluss bietet, die Zustandsverwaltung simplifiziert und Components auf der Grundlage von Zustandsänderungen neu rendert. Obendrein haben wir Zugang zu einem umfangreichen und leistungsstarken Bibliotheks-Ökosystem, besseren Entwickler-Support, Dokumentation und Community-Ressourcen.

React erleichtert auch die UI-Entwicklung durch JSX. Dank der Components-basierten Architektur und der Hooks lässt sich mit React die Geschäftslogik viel sauberer strukturieren.

Zudem ist React zu benutzen eine wahre Freude, deshalb haben wir es für die Entwicklung unserer Komponenten bevorzugt.

Wie wir es gemacht haben

Es stellte sich heraus, dass es ziemlich simpel war. React-to-web-component ist ein npm-Paket, das React-Components in benutzerdefinierte Elemente umwandelt und es ermöglicht, sie als native Elemente zu verwenden, die nicht über React eingebunden werden müssen.

import r2wc from "@r2wc/react-to-web-component"

const MyComponent = r2wc(ReactComponent)

customElements.define("my-component", MyComponent)

Wir haben Webpack verwendet, um die JS-Datei zu erstellen, sie in die Vorlage geladen und die Web-Component so aufgerufen, als wäre sie ein nativer Tag.

<body>
   <h1>Testing</h1>
   <my-component></my-component>
</body>

Übergabe von Attributen

Wir sind beinahe fertig, aber ein Teil fehlt noch, die Möglichkeit, Props aus dem Template zu übergeben. Zum Glück ist auch das einfach gemacht.

Zuerst müssen die Props zusammen mit der Web-Component definiert werden, wie im folgenden:

const MyComponent = r2wc(ReactComponent, {
   props: {
       schoolId: 'number',
       readOnly: 'boolean'
   }
})

Einige Anmerkungen an dieser Stelle

  1. Die Namen werden in Camel-Case geschrieben, in der Vorlage werden sie jedoch in Kebab-Case geschrieben.
  2. Die Werte beziehen sich auf den Typ der übergebenen Daten und können eine Zeichenkette, eine Zahl, ein Boolean, ein Array, ein Objekt und sogar eine Funktion sein.

Mehr über diese Typen kannst du hier lesen.

Bei der Verwendung der Attribute geht es nun nur noch darum, die Daten an die Web-Component zu übergeben:

<my-component
   school-id="{{ school.id }}"
   read-only="{{ read_only }}"/>

Zwar übergeben wir hier Werte von Django an die Web-Component, aber dieser Ansatz funktioniert mit jedem Backend oder jeder Template-Engine, nicht nur mit Django. Die Werte werden als Strings übergeben, die dann von r2wc geparst und als JS-Primitive oder Objekte an die React-Komponente weitergegeben werden.

Verknüpfung von Django und React

Wie bereits erwähnt, verwenden wir Webpack, wie genau? Nun die Kurzversion ist, dass man, wenn man das Frontend baut, das Skript einfach in sein Template einbinden kann und es funktioniert einfach.

Aber wir wollten auch die Entwicklung einfacher machen, also wollten wir, dass es im Entwicklungsmodus funktioniert, mit dem automatischen Neuladen bei Änderungen.

devServer: {
   ...
   liveReload: true,
   devMiddleware: {
       writeToDisk: true
   }
   ...
}

Wie beim letzten Mal haben wir Webpack verwendet, um bei Änderungen am Frontend alles auf der Festplatte zu speichern und dann einfach Django das Frontend-Build-Verzeichnis als statische Dateien verwenden lassen.

Wie beim letzten Mal haben wir das HTMLWebpackPlugin und das MiniCssExtractPlugin verwendet, um unsere JS- und CSS-Dateien zu generieren.

Fazit

Das Endergebnis war, dass wir unsere React-Components für unsere Django-Anwendung als Web-Components zur Verfügung gestellt hatten.

Wir haben zunächst nur ausprobiert, ob es möglich ist, und letztendlich hatten wir eine funktionierende Lösung, die sehr einfach umzusetzen war. Die Verwendung von React-Components als Web-Components war das Sahnehäubchen und fügte unserem Werkzeugkasten ein neues Werkzeug hinzu.

Das war jedoch nicht das einzige Neue, das wir mit unserem Projekt ausprobiert haben. Wir haben auch eine bessere API wie ein Ninja erstellt, über die du ebenfalls in unserem Blog lesen kannst.

djangsters GmbH

Vogelsanger Straße 187
50825 Köln

Sortlist