Kodokon kodokon.com

Context: teilen ohne Prop Drilling

Bewege Daten ohne Prop Drilling mithilfe von Context und behalte dabei seine erneuten Renderings und Grenzen im Griff.

8 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

Prop Drilling bezeichnet jene Props, die durch drei oder vier zwischengeschaltete Komponenten gereicht werden, ohne dort verwendet zu werden, nur um ein Blatt des Baums zu erreichen. Jede Schicht, die sie durchqueren, wird an Daten gekoppelt, die sie nicht interessieren. Context löst dieses Transportproblem: createContext erstellt den Kanal, ein Provider veröffentlicht einen Wert, und useContext liest ihn aus jedem Nachfahren. Behalte die Nuance im Kopf: Context transportiert Zustand, er verwaltet ihn nicht - der Wert lebt weiterhin in einer Komponente, hier im Provider.

JSX
import { createContext, useMemo, useState } from "react";

const ThemeContext = createContext(null);

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("dark");
  const value = useMemo(
    () => ({ theme, setTheme }),
    [theme]
  );

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}
Der veröffentlichte Wert wird memoisiert, um seine Referenz stabil zu halten.

Wenn sich der Wert des Providers ändert, rendern alle Consumer neu, selbst tief verschachtelte - und React.memo schützt sie nicht, weil es Props vergleicht, nicht Context. Die klassische Falle verschlimmert das Ganze: value={{ theme, setTheme }} zu schreiben erzeugt bei jedem Rendern des Providers ein frisches Objekt, also eine neue Referenz, also kaskadierende erneute Renderings, selbst wenn sich theme nicht bewegt hat. Genau dafür ist das useMemo oben da. Runde das Ganze mit einem Zugriffs-Hook ab, der außerhalb des Providers laut fehlschlägt.

JSX
import { useContext } from "react";

function useTheme() {
  const context = useContext(ThemeContext);
  if (context === null) {
    throw new Error(
      "useTheme must be inside <ThemeProvider>"
    );
  }
  return context;
}
Ein expliziter Fehler ist besser als ein stummes null.

Bevor du einen Context erstellst, schöpfe zuerst die Möglichkeit der Komposition aus. Wenn eine zwischengeschaltete Schicht nichts weiter tut, als eine Prop weiterzureichen, kehre die Struktur um: Baue das Element an der Spitze des Baums, wo die Daten leben, und reiche es über children oder eine dedizierte Prop nach unten. Die zwischengeschaltete Schicht wird generisch und die Daten laufen durch keine Komponente mehr, die sie nicht verwendet.

JSX
function App({ currentUser }) {
  return (
    <Layout
      header={<TopBar user={currentUser} />}
      content={<Dashboard />}
    />
  );
}
Layout weiß nichts von currentUser: Es fügt fertige Elemente ein.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Ein Context-Consumer ist in React.memo eingehüllt und der Context-Wert ändert sich. Was passiert?
    • Er rendert nicht neu: React.memo blockiert alle Aktualisierungen
    • Er rendert neu: React.memo vergleicht Props, nicht Context-Werte
    • Er rendert nur neu, wenn sich auch seine Props ändern
  2. Warum das Objekt memoisieren, das in einem Provider an value übergeben wird?
    • Um die Erstellung des Objekts bei jedem Rendern zu beschleunigen
    • Weil der Provider einen unveränderlichen Wert verlangt
    • Damit sich die Referenz nur ändert, wenn sich die Daten ändern, und unnötige erneute Renderings der Consumer vermieden werden
  3. Was ist die wichtigste Einschränkung von Context bei sehr häufig aktualisiertem Zustand?
    • Keine Selektoren: Jede Aktualisierung rendert alle Consumer neu, selbst jene, die nur einen Teil des Werts verwenden
    • Context ist asynchron und verzögert Aktualisierungen
    • Context kann nur primitive Werte speichern