12k
All articles

5 Dinge, für die Sie React nicht benötigen

Fünf native Browser-APIs ersetzen typische React-Komponenten: dialog, Popover, Custom Elements, Container Queries und View Transitions.

OpenReplay Team
OpenReplay Team
5 Dinge, für die Sie React nicht benötigen

Die Browserplattform hat native, allgemein verfügbare Ersetzungen für mehrere UI-Primitive ausgeliefert, die früher React-Komponenten oder Drittanbieter-Bibliotheken erforderten: modale Dialoge, Popovers und Tooltips, framework-agnostische wiederverwendbare Widgets, container-bewusste responsive Layouts und animierte View Transitions. Dieser Artikel ist kein Argument gegen React — es bleibt das richtige Werkzeug für komplexen gemeinsamen Zustand, umfangreiche Formular-Workflows und Ökosysteme wie Next.js und Remix. Es handelt sich um eine Prüfliste für Maintainer: fünf Kategorien von Komponenten, die Sie möglicherweise bereits in einer React-Codebasis haben und die der Browser nun nativ verarbeiten kann — ohne zusätzliches JavaScript-Bundle-Gewicht.

Die Zielgruppe sind React-Entwickler im Arbeitsalltag, deren mentales Modell davon, „was der Browser leisten kann”, irgendwo um das Jahr 2020 aufgehört hat, sich zu aktualisieren. React 19 ist das aktuelle Stable Release, und mehrere der Muster, die sein Ökosystem gelöst hat, sind nun Teil der Plattform. Jeder der folgenden Abschnitte benennt das React-Reflexmuster, die native API, die es ersetzt, und den spezifischen Accessibility-Vorbehalt, den Sie kennen müssen, bevor Sie Code löschen.

Wesentliche Erkenntnisse

  • Das HTML-Element <dialog> mit showModal() bietet natives Focus Trapping, Escape-Taste zum Schließen und ein ::backdrop-Pseudo-Element — und beseitigt damit die meisten Gründe, von einer React-Modal-Bibliothek abhängig zu sein.
  • Die Popover API rendert Elemente im Top Layer des Browsers, wodurch die gesamte Klasse von z-index- und overflow: hidden-Clipping-Fehlern entfällt, die handgeschriebene React-Tooltips und Dropdowns plagen.
  • Custom Elements mit Shadow DOM ermöglichen es Ihnen, ein einzelnes Widget auszuliefern, das in jedem Framework oder in reinem HTML funktioniert — ohne es pro Stack neu zu implementieren.
  • CSS Container Queries (@container) ermöglichen es einer Komponente, auf die Breite ihres übergeordneten Elements zu reagieren, und ersetzen damit ResizeObserver-Hooks und React-Zustand, der ausschließlich für Layout-Entscheidungen verwendet wird.
  • Die View Transitions API (document.startViewTransition()) animiert DOM-Zustandsänderungen nativ und deckt viele Anwendungsfälle ab, die zuvor von Framer Motion oder react-transition-group übernommen wurden.

Modals: Verwenden Sie das <dialog>-Element anstelle einer Modal-Bibliothek

Für modale Dialoge bietet das native HTML-Element <dialog>, aufgerufen über showModal(), Focus Trapping, inerten Hintergrundinhalt, Escape-Taste zum Schließen und Backdrop-Styling — Verhaltensweisen, die ein benutzerdefiniertes React-Modal manuell implementieren muss und dabei häufig Fehler macht. Das <dialog>-Element ist Teil von Baseline; überprüfen Sie das genaue Verfügbarkeitsdatum auf MDN, bevor Sie interne Dokumentationen veröffentlichen.

Das React-Muster. Teams greifen typischerweise auf react-modal, Radix Dialog oder einen benutzerdefinierten useModal-Hook zurück, der auf einem Portal basiert. Das Hook-Muster kombiniert üblicherweise createPortal, einen useEffect, der document.body.style.overflow umschaltet, und eine handgeschriebene Focus Trap. Produktions-Session-Replays dieser Implementierungen zeigen häufig Nutzer, die mit der Tab-Taste aus dem Modal in Hintergrundinhalte navigieren — ein Symptom unvollständiger Focus-Trap-Logik.

Die native API.

<dialog id="confirm" aria-labelledby="confirm-title">
  <h2 id="confirm-title">Delete project?</h2>
  <p>This action cannot be undone.</p>
  <form method="dialog">
    <button value="cancel">Cancel</button>
    <button value="confirm">Delete</button>
  </form>
</dialog>

<script>
  document.getElementById('confirm').showModal();
</script>

showModal() platziert den Dialog im Top Layer, sperrt den Fokus darin, macht den Rest des Dokuments inert und rendert das ::backdrop-Pseudo-Element, das Sie mit CSS gestalten können. Ein <form method="dialog"> schließt den Dialog und gibt den value des geklickten Buttons über dialog.returnValue zurück — kein Event-Listener erforderlich.

Vorbehalte. Der Accessibility-Fallstrick besteht darin, dass <dialog> kein Label automatisch ankündigt. Sie benötigen aria-labelledby, das auf eine sichtbare Überschrift zeigt (oder aria-label), damit Screenreader es identifizieren können. Wenn der Dialog nicht-modal ist — also mit show() statt showModal() geöffnet wird — sperrt er den Fokus nicht, und Sie sollten stattdessen die Popover API in Betracht ziehen. React oder eine Bibliothek bleibt die bessere Wahl, wenn Sie deklarative, zustandsgebundene Öffnen/Schließen-Logik benötigen, die eng mit anderen Komponenten gekoppelt ist, oder wenn Animationen ausgeführt werden müssen, bevor der Dialog aus dem DOM entfernt wird.

Popovers, Tooltips und Dropdowns: Verwenden Sie die Popover API

Die Popover API rendert Elemente im Top Layer des Browsers, was bedeutet, dass ein Popover unabhängig vom Stacking-Kontext oder overflow: hidden eines übergeordneten Elements immer über anderen Inhalten erscheint. Dies beseitigt die gesamte Kategorie von z-index-Konflikten und Clipping-Fehlern, die handgeschriebene Tooltip- und Dropdown-Implementierungen verursachen.

Das React-Muster. Floating UI, Radix Popover und die Overlay-Primitive von React-Aria sind gängige Abhängigkeiten. Sie übernehmen Positionierung, Schließen bei Klick außerhalb und Portal-Rendering. Für einen einfachen Tooltip ist das eine Menge zu importierender Code.

Die native API.

<button popovertarget="menu">Open menu</button>

<div id="menu" popover>
  <a href="/account">Account</a>
  <a href="/logout">Log out</a>
</div>

Das popover-Attribut allein — ohne JavaScript — gibt Ihnen ein Element, das über einen popovertarget-Button umgeschaltet wird, sich bei Klick außerhalb und Escape schließt und im Top Layer rendert. Der Standardwert popover="auto" aktiviert Light-Dismiss; popover="manual" deaktiviert ihn für Fälle, in denen Sie explizite Kontrolle wünschen. Die Popover API ist Baseline Newly Available; überprüfen Sie die MDN-Kompatibilitätstabelle für den aktuellen Status.

Vorbehalte. Der Accessibility-Fallstrick besteht darin, dass die Popover API im Gegensatz zu showModal() bei <dialog> den Fokus nicht automatisch verwaltet. Wenn Ihr Popover funktional ein Menü ist, müssen Sie dennoch role="menu" anwenden, Roving Tabindex verwalten und den Fokus beim Öffnen in das Popover verschieben. Für die Positionierung relativ zum auslösenden Element benötigen Sie außerdem CSS Anchor Positioning, dessen Baseline-Status eingeschränkter ist — überprüfen Sie dies auf MDN, bevor Sie sich browserübergreifend darauf verlassen. Für komplexe Menüs mit Untermenüs, Tastaturnavigationsmustern und Typeahead spart eine Bibliothek wie Radix oder React-Aria nach wie vor echte Arbeit.

Wiederverwendbare Widgets: Verwenden Sie Custom Elements und Shadow DOM

Ein Custom Element, das mit customElements.define() registriert wurde, funktioniert in jedem HTML-Kontext — React, Vue, Angular, Svelte oder einer einfachen HTML-Datei — ohne Neuimplementierung. In Kombination mit Shadow DOM bietet es Style-Kapselung ohne CSS Modules, CSS-in-JS oder einen Build-Schritt. Custom Elements und Shadow DOM sind Baseline Widely Available; überprüfen Sie das Jahr auf MDN.

Web Components haben React in der Mainstream-Anwendungsentwicklung nicht ersetzt. Was sie ersetzt haben, ist die Notwendigkeit, dasselbe Widget fünfmal auszuliefern — einmal pro Framework — wenn Sie ein Design-System pflegen oder ein Drittanbieter-Embed verteilen.

Das React-Muster. Ein wiederverwendbarer Button, Badge oder Chart, in eine React-Komponente verpackt, auf npm veröffentlicht und für jedes Team, das ein anderes Framework verwendet, neu implementiert (oder neu umhüllt).

Die native API.

class CopyButton extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' }).innerHTML = `
      <style>button { padding: 6px 12px; }</style>
      <button><slot>Copy</slot></button>
    `;
    this.shadowRoot.querySelector('button')
      .addEventListener('click', () => {
        navigator.clipboard.writeText(this.dataset.value ?? '');
      });
  }
}
customElements.define('copy-button', CopyButton);

Wird als <copy-button data-value="hello">Copy</copy-button> in beliebigem HTML verwendet, einschließlich innerhalb von JSX. React 19 unterstützt Custom Elements direkt, einschließlich der Übergabe von Objekt-Props und dem Lauschen auf Custom Events.

Vorbehalte. Der Accessibility-Fallstrick besteht darin, dass der Accessibility-Tree standardmäßig keine Shadow-Grenzen durchdringt — aria-labelledby- und aria-describedby-Referenzen im Light DOM können keine IDs innerhalb eines Shadow Root ansprechen und umgekehrt. Die ARIA in HTML-Spezifikation und der in Entwicklung befindliche Reference Target Proposal adressieren dies, aber praktische Muster erfordern heute entweder explizite ARIA-Attribute am Host-Element oder attachInternals() mit ElementInternals. React ist nach wie vor die bessere Wahl, wenn ein Widget eng mit dem Anwendungszustand integriert werden muss, React Context teilt oder Suspense verwendet.

Responsives Layout auf Komponentenebene: Verwenden Sie CSS Container Queries

CSS Container Queries (@container) ermöglichen es einer Komponente, ihr Layout basierend auf der Breite ihres eigenen übergeordneten Elements anzupassen, anstatt auf den Viewport. Dies beseitigt das useResizeObserver-Hook-Muster, bei dem React-Zustand Container-Dimensionen ausschließlich zur Steuerung eines Klassennamens verfolgt. Container Queries sind Baseline Widely Available — überprüfen Sie das Jahr auf MDN.

Das React-Muster. Ein useResizeObserver-Hook (häufig aus @react-hook/resize-observer oder handgeschrieben), der mit dem Komponentenzustand verbunden ist und eine layout="compact"-Prop oder einen Klassennamen austauscht. Jede Größenänderung löst ein React-Render aus, obwohl der einzige Konsument CSS ist.

Die native API.

.card-container {
  container-type: inline-size;
}

.card {
  display: grid;
  grid-template-columns: 1fr;
}

@container (min-width: 400px) {
  .card {
    grid-template-columns: 120px 1fr;
  }
}

Deklarieren Sie container-type: inline-size am übergeordneten Element, und schreiben Sie dann @container-Regeln für das untergeordnete Element. Der Browser übernimmt die Größenänderungsbeobachtung nativ. Kein JavaScript, keine Re-Renders, kein Hydration-Mismatch.

Der :has()-Selektor ergänzt dies für zustandsbewusstes Styling. Eine Regel wie form:has(input:invalid) button[type="submit"] { opacity: 0.5 } drückt aus, was zuvor useState und ein Controlled-Input-Muster erforderte. :has() ist Baseline Widely Available — überprüfen Sie MDN.

Vorbehalte. Die Accessibility-Überlegung ist subtil, aber real: Container Queries können das Layout erheblich ändern, ohne die DOM-Reihenfolge zu ändern, was für Screenreader gut ist, aber bedeutet, dass Sie die Lesereihenfolge bei jedem Breakpoint noch auf Übereinstimmung mit der visuellen Reihenfolge überprüfen sollten. Container Queries führen außerdem Containment-Verhalten ein, das beeinflussen kann, wie untergeordnete Elemente angeordnet und positioniert werden — testen Sie daher Komponenten, die auf viewport-relative Positionierung oder andere Layout-Annahmen angewiesen sind. React-Zustand ist nach wie vor gerechtfertigt, wenn die Layout-Entscheidung mehr als nur Styling steuert — zum Beispiel wenn Sie einen anderen Komponentenbaum rendern müssen, nicht nur einen neu gestalten.

Animierte Übergänge: Verwenden Sie die View Transitions API

Die View Transitions API umhüllt ein DOM-Update standardmäßig mit einer Cross-Fade-Animation, mit voller CSS-Kontrolle über den Übergang über ::view-transition-*-Pseudo-Elemente. Für Same-Document-Transitions deckt sie die Mehrheit der Routen- und Zustandsübergangs-Animationen ab, die zuvor Animations-Bibliotheken erforderten.

Das React-Muster. Framer Motion, react-transition-group oder AnimatePresence-Wrapper um Routen-Komponenten. Diese funktionieren, erfordern aber, dass die Animation im Render-Modell von React ausdrückbar ist, was für Übergänge, die das Unmounten eines Baums und das Mounten eines anderen umspannen, umständlich ist.

Die native API.

function navigate(url) {
  if (!document.startViewTransition) {
    updateDOM(url);
    return;
  }
  document.startViewTransition(() => updateDOM(url));
}

document.startViewTransition() nimmt einen Callback entgegen, der das DOM-Update durchführt. Der Browser erfasst den Vorher-Zustand, führt den Callback aus, erfasst den Nachher-Zustand und blendet zwischen ihnen über. Um ein bestimmtes Element über den Übergang hinweg zu animieren — zum Beispiel ein Thumbnail, das sich zu einer Detailansicht erweitert — geben Sie übereinstimmenden Elementen in CSS denselben view-transition-name. Same-Document View Transitions sind Baseline Newly Available; Cross-Document View Transitions (für MPA-Navigationen) haben eingeschränkteren Support — überprüfen Sie die MDN-Kompatibilitätstabelle und den WebKit-Blog für den aktuellen Safari-Status, bevor Sie sich auf den Cross-Document-Modus verlassen.

Vorbehalte. Der Accessibility-Fallstrick ist Bewegung: Respektieren Sie prefers-reduced-motion, indem Sie den Übergang in eine Media Query einbetten oder den Aufruf für Nutzer, die sich dagegen entscheiden, vollständig überspringen. Der Standard-Cross-Fade ist kurz, aber dennoch eine Animation. React-Bibliotheken bleiben die bessere Wahl, wenn Sie Federmechanik-Physik, gestengesteuerte Übergänge oder Animationen benötigen, die mitten im Ablauf unterbrochen und umgekehrt werden können — View Transitions sind atomar und nicht dafür ausgelegt.

Wo React nach wie vor überlegen ist

Die fünf oben genannten Austausche zielen auf spezifische Komponentenkategorien ab. Für alles Folgende ist React nach wie vor das richtige Werkzeug, und es würde mehr kosten als es einsparen würde, es durch Plattform-Features zu ersetzen.

  • Komplexer gemeinsamer Zustand über weit entfernte Komponenten hinweg. Wenn mehrere nicht verwandte Teile der UI denselben sich entwickelnden Zustand mit abgeleiteten Selektoren abonnieren, leisten Bibliotheken wie Zustand, Jotai oder Redux Toolkit Arbeit, die die Plattform nicht übernimmt. Custom Events auf Web Components können Daten transportieren, modellieren aber keinen abgeleiteten Zustand.
  • Umfangreiche Formular-Workflows mit feldübergreifender Validierung und dynamischem Rendering. Natives <form>, die Constraint Validation API und FormData verarbeiten die Übermittlung einzelner Formulare sauber. Mehrstufige Wizards, bedingte Felder, die von Werten anderer Formularstellen abhängen, serverseitige Validierung, die mit clientseitiger Validierung zusammengeführt wird, und Feld-Arrays profitieren nach wie vor von React Hook Form oder TanStack Form.
  • Servergesteuertes Rendering und Datenabruf. React Server Components, der use()-Hook für asynchrone Daten und das Streaming-SSR-Modell in Next.js und Remix lösen Hydrations-, Code-Splitting- und Datenabruf-Koordinationsprobleme, die die Plattform nicht direkt adressiert.
  • Ökosystem-Reife für Routing und Datenschichten. TanStack Router, TanStack Query und das etablierte React Router-Ökosystem bieten Cache-Invalidierung, optimistische Updates und Route-Loader-Muster, deren Replikation gegen native APIs erheblichen Aufwand erfordern würde.
  • Team-Konventionen und bestehende Investitionen. Eine Codebasis, eine Einstellungs-Pipeline, ein Design-System und eine CI-Umgebung, die um React herum aufgebaut wurden, sind selbst ein Vermögenswert. Der hier beschriebene Prüfansatz besteht darin, spezifische Komponenten zu entfernen, bei denen die Plattform nun ausreicht — nicht darin, Stacks zu migrieren.

Die praktische Maßnahme: Öffnen Sie Ihr größtes React-Komponentenverzeichnis und suchen Sie nach Modal, Popover, Tooltip, Dropdown und allen useResizeObserver-Importen. Jeder davon ist ein Kandidat für den oben beschriebenen nativen Ersatz. Überprüfen Sie den Baseline-Status der API gegen MDN für Ihren unterstützten Browser-Bereich, liefern Sie den Austausch hinter einem Feature-Flag aus und messen Sie das Bundle-Delta. Der Browser hat aufgeholt — die verbleibende Arbeit besteht darin zu prüfen, welche Abhängigkeiten Sie nicht mehr benötigen.

Häufig gestellte Fragen

Kann ich das native dialog-Element mit Reacts Zustandsmodell verwenden?

Ja. Hängen Sie eine Ref an das dialog-Element und rufen Sie ref.current.showModal() oder ref.current.close() aus Effects auf, die von React-Zustand gesteuert werden. Der Dialog bleibt im React-Baum und akzeptiert JSX-Kinder normal, aber Sie umgehen zustandsgesteuerte open-Props auf der gerenderten Ausgabe. Die größte Reibung besteht darin, dass React Effects nicht für das interne cancel-Event des Dialogs erneut ausführt — hängen Sie daher einen nativen close-Listener über useEffect an, um den Zustand zurückzusynchronisieren.

Wie übergeben Custom Elements komplexe Daten an React und umgekehrt?

React 19 übergibt Nicht-String-Props direkt an Custom-Element-Eigenschaften, anstatt sie zu Attributen zu serialisieren, sodass Objekte und Arrays ohne JSON-Kodierung funktionieren. Custom Elements geben Daten über CustomEvent zurück, auf die React 19 mit Standard-on-Präfix-Handler-Props lauscht (zum Beispiel onMyEvent). In React 18 und früher müssen Sie Event-Listener imperativ über eine Ref anhängen, da synthetische Events keine benutzerdefinierten Event-Namen verarbeiten.

Beeinträchtigen Container Queries und der :has()-Selektor die Rendering-Performance?

Beide haben messbare Kosten, sind aber im Allgemeinen günstiger als die JavaScript-Alternativen, die sie ersetzen. Container Queries erfordern, dass der Browser einen Containment-Kontext pflegt und übereinstimmende Regeln bei Größenänderungen neu auswertet, was immer noch schneller ist als ein ResizeObserver-Callback, der ein React-Render auslöst. Der :has()-Selektor kann teuer sein, wenn er mit breiten Subject-Selektoren über große DOM-Bäume hinweg verwendet wird; begrenzen Sie ihn auf spezifische übergeordnete Elemente, anstatt ihn auf body- oder Root-Level-Elemente anzuwenden.

Funktioniert die View Transitions API mit clientseitigen Routern wie React Router?

Ja, für Same-Document-Transitions. Umhüllen Sie den Navigations-Callback Ihres Routers mit document.startViewTransition(), sodass das DOM-Update, das React während des Routenwechsels durchführt, innerhalb des Übergangs ausgeführt wird. React Router v6 und TanStack Router unterstützen dieses Muster beide durch Navigationsinterception. Cross-Document View Transitions, die vollständige Seitenladevorgänge animieren, erfordern zusätzliches Opt-in über die @view-transition CSS-Regel und haben eingeschränkteren Browser-Support — überprüfen Sie MDN, bevor Sie sich darauf verlassen.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.