Auf dieser Seite
Einbettbare React-Widgets: schnell, navigierbar, barrierefrei
Eine eigene Full-Stack-React-Anwendung kontrolliert alles: das Routing, den Server, das Laden der Daten. Eine Frontend-Widget-Bibliothek kontrolliert davon nichts. Sie wird als Paket ausgeliefert und läuft in einer Website, die in meinem Kundenprojekt einem anderen Team gehört, mit eigenem Framework und eigenem Content-Management-System.
Ich entwickle seit zwei Jahren eine solche Bibliothek für einen mehrstufigen Kaufprozess. Vier Dinge haben dabei den größten Unterschied gemacht.
1. Serverseitig rendern, ohne den Server zu besitzen
Ein Widget, das erst im Browser seine Daten lädt, zeigt zuerst einen Ladezustand – etwa in Form von Ladeanimationen oder Skeletons. Ein Skeleton ist ein Platzhalterinhalt, der ersetzt wird, sobald der eigentliche Inhalt mit Verzögerung geladen wurde. Für den wichtigsten Inhalt der Seite ist das zu spät. Die Bibliothek liefert deshalb neben den Komponenten einen eigenen Einstieg für den Server mit einem Loader je Widget. Er lädt die essenziellen Daten vorab, sodass die Komponenten schon auf dem Server mit Inhalt gerendert werden. Im Browser übernimmt React das fertige HTML und macht es interaktiv. Dieser Schritt heißt Hydration:
// Server: Daten laden und als Zustand mitgeben
export async function loadDashboard(ctx: LoaderContext) {
const queryClient = new QueryClient();
await queryClient.prefetchQuery(cartQuery(ctx.cartId, ctx.auth));
return dehydrate(queryClient);
}
// Website: Zustand an das Widget übergeben
<HydrationBoundary state={state}>
<DashboardWidget />
</HydrationBoundary>
Die Website ruft den Loader auf ihrem Server auf und reicht das Ergebnis durch. Das Widget rendert sofort mit Daten und übernimmt im Browser nahtlos.
2. Den React Compiler einführen und seine Grenzen kennen
Der React Compiler übernimmt das Memoisieren, das man früher mit useMemo und
useCallback von Hand geschrieben hat. In einer Anwendung mit vielen
Formularen spart das spürbar Renderarbeit.
Er setzt aber voraus, dass sich Komponenten an die Regeln von React halten. Ein Fehler, der dabei aufgetreten ist:
const value = watch('quantity'); // wird nach dem Umstieg nicht mehr aktualisiert
const value = useWatch({ control, name: 'quantity' }); // funktioniert
watch() aus der Bibliothek react-hook-form liefert einen Wert, dessen Änderung der Compiler nicht erkennt. Die Komponente zeigt dann einen veralteten Stand.
useWatch() ist ein echter Hook und verhält sich richtig. Solche Stellen findet man nur mit dem nötigen Spezialwissen oder durch Tests, die Eingaben wirklich durchspielen.
3. Navigation gehört zur Website, der Verlauf zum Browser
Ein mehrstufiger Ablauf mit Unterschritten, etwa je Person oder je Artikel, muss drei Dinge aushalten: Zurück, Vorwärts und Neuladen. Die Widgets halten ihren Schritt deshalb nicht in eigenem Zustand, sondern im Verlauf des Browsers:
history.pushState({ step: 'options', item: 2 }, '');
window.addEventListener('popstate', (e) => showStep(e.state));
Welche Hauptschritte es gibt und wie sie heißen, bestimmt die Website. Zwischen Website und Bibliothek steht dafür ein kleiner Vertrag: Die Website nennt die Schritte, die Bibliothek meldet, wo sie steht und welche Unterschritte es gibt. Beide Seiten können sich ändern, solange der Vertrag hält.
4. Barrierefreiheit in jedem Test
Barrierefreiheit, die nur vor einem Audit geprüft wird, ist beim nächsten Feature wieder kaputt. Bei mir läuft die Prüfung in den normalen Komponententests mit:
const { container } = render(<OptionSelection />);
expect(await axe(container)).toHaveNoViolations();
Das findet nicht alles. Fehlende Beschriftungen, falsche Rollen und unzureichende Kontraste findet es aber zuverlässig, und zwar am selben Tag, an dem sie entstehen. Dazu kommt eine feste Schwelle von 80 % Testabdeckung, unter der kein Pull Request durchgeht, was ein diskutabler Ansatz ist, sich in der Praxis in diesem Kundenprojekt dennoch bewährt hat.
Was eine Bibliothek sonst noch anders macht
- React ist eine Peer-Abhängigkeit. Die Bibliothek bringt React nicht mit, sondern nutzt die Version der Website. Zwei React-Versionen auf einer Seite sind ein Fehler, den man lange sucht.
- Jede Version ist ein Versprechen. Ein Changelog je Release und semantische Versionen sind hier keine Formalie, weil ein anderes Team die Version einbaut.
- Eine Demo-Anwendung gehört dazu. Sie zeigt jedes Widget ohne die integrierende Website und ist die schnellste Art, einen Fehler bereits in der Entwicklung einzugrenzen.
Mehr zum fachlichen Hintergrund steht im Fallbeispiel.