12k
All articles

React-Effects finden, die Sie nicht brauchen

Mit eslint-plugin-react-you-might-not-need-an-effect erkennen Sie unnötige React-Effekte und refaktorieren abgeleiteten State, Event-Handler und prop-basierte Resets.

OpenReplay Team
OpenReplay Team
React-Effects finden, die Sie nicht brauchen

eslint-plugin-react-you-might-not-need-an-effect ist ein Plugin für ESLint und Oxlint. Es markiert useEffect-Aufrufe, die laut React-Dokumentation unnötig sind, etwa Effects, die State ableiten, Events weiterreichen oder Daten an eine Elternkomponente übergeben.

Sie haben You Might Not Need an Effect gelesen und zustimmend genickt. Dann öffnen Sie eine Codebasis mit vierhundert useEffect-Aufrufen und haben keine praktikable Möglichkeit herauszufinden, welche davon das Problem sind.

Dieser Artikel zeigt, wie Sie das Plugin installieren, was es bei einer realistischen Komponente meldet, wie Sie die vier häufigsten Befunde refaktorieren und wann eine Warnung unterdrückt statt behoben werden sollte. Zur Auffrischung über den Hook selbst lesen Sie diesen Leitfaden zum useEffect-Hook von React.

Die wichtigsten Erkenntnisse

  • eslint-plugin-react-you-might-not-need-an-effect bringt neun Regeln und zwei Flat-Config-Presets mit: recommended meldet jede Regel als Warnung, strict meldet jede Regel als Fehler. Für Legacy-Projekte mit .eslintrc gibt es die entsprechenden Presets legacy-recommended und legacy-strict.
  • Berechnen Sie Werte, die nur von aktuellen Props und aktuellem State abhängen, während des Renderns, statt sie per Effect in den State zu kopieren.
  • Um den gesamten State einer Komponente zurückzusetzen, wenn sich eine Prop wie eine Kunden-ID ändert, rendern Sie die Komponente mit dieser Prop als key.
  • Effects, die React mit einem externen System synchron halten, etwa einem Timer, einer DOM-API, einem Nicht-React-Widget oder einer Netzwerkverbindung, sind genau die Aufgabe, für die es useEffect gibt. Behalten Sie sie bei.
  • Unterdrücken Sie eine Warnung mit einem eslint-disable-next-line-Kommentar, der die Regel benennt und nach -- eine Begründung angibt.

Was macht einen React-Effect unnötig?

Die meisten unnötigen Effects treten in zwei Formen auf: Ein Effect berechnet einen Wert neu und speichert ihn im State, oder ein Effect beobachtet einen Teil des States und passt einen anderen daran an. In beiden Fällen committet React einen Render mit dem alten Wert, führt den Effect aus und rendert anschließend erneut mit dem korrigierten Wert.

Der zusätzliche Render, den ein unnötiger Effect verursacht, ist für Nutzer sichtbar. Session Replays von Effect-lastigen Komponenten können für einen Frame eine leere gefilterte Liste oder eine Platzhalter-Summe zeigen, bevor der zweite Render sie füllt. Die Lösung besteht fast immer darin, den State zu entfernen, statt den Effect zu optimieren.

Plugin installieren und ausführen

Das Plugin wird als Dev-Dependency installiert und über sein recommended-Preset in die ESLint-Flat-Config eingebunden. Wenn Sie bereits mehrere ESLint-Plugins verwalten, lässt es sich konfliktfrei daneben betreiben.

npm install --save-dev eslint-plugin-react-you-might-not-need-an-effect
// eslint.config.js
import reactYouMightNotNeedAnEffect from "eslint-plugin-react-you-might-not-need-an-effect";
import globals from "globals";

export default [
  reactYouMightNotNeedAnEffect.configs.recommended,
  {
    languageOptions: {
      globals: { ...globals.browser },
      parserOptions: { ecmaFeatures: { jsx: true } },
    },
  },
];

Die Beispiele beziehen sich auf Version 1.0.2, die laut CHANGELOG des Plugins am 20. August 2026 veröffentlicht wurde. Aus demselben Changelog geht hervor, dass no-pass-ref-to-parent und no-empty-effect vor Version 1.0 entfernt wurden. Löschen Sie diese Regeln also aus älteren Konfigurationen. Oxlint-Nutzer laden das Plugin über das jsPlugins-Feld von Oxlint. Regel-IDs verwenden das Kebab-Case-Präfix react-you-might-not-need-an-effect/, und zwar in Oxlint, in Legacy-Konfigurationen sowie in der Flat-Config, wenn Sie die Presets nutzen, da die Presets das Plugin unter seinem Paketnamen registrieren. Das CamelCase-Präfix reactYouMightNotNeedAnEffect/ gilt nur, wenn Sie das Plugin selbst unter diesem Schlüssel registrieren, wie es das Einzelregel-Beispiel in der README tut.

Hier ist eine Komponente mit vier Effects, von denen jeder dem dokumentierten Muster einer Regel entspricht:

import { useEffect, useState } from "react";
import { postOrder, useCustomerHistory } from "./api";

export function OrderPanel({ customerId, items, onLoaded }) {
  const [total, setTotal] = useState(0);
  const [submitted, setSubmitted] = useState(false);
  const [note, setNote] = useState("");
  const history = useCustomerHistory(customerId);

  // Effect 1
  useEffect(() => {
    setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));
  }, [items]);

  // Effect 2
  useEffect(() => {
    if (submitted) postOrder(customerId, items, note);
  }, [submitted, customerId, items, note]);

  // Effect 3
  useEffect(() => {
    onLoaded(history);
  }, [history, onLoaded]);

  // Effect 4
  useEffect(() => {
    setNote("");
    setSubmitted(false);
  }, [customerId]);

  return (
    <form onSubmit={(e) => { e.preventDefault(); setSubmitted(true); }}>
      <textarea value={note} onChange={(e) => setNote(e.target.value)} />
      <p>Total: {total}</p>
      <button type="submit">Place order</button>
    </form>
  );
}

Führen Sie npx eslint src/OrderPanel.jsx aus. Mit recommended erscheinen die Befunde als Warnungen, jeweils mit der Regel-ID am Zeilenende. Jeder Effect lässt sich der Regel zuordnen, die er auslöst:

EffectMusterRegel
1Aus Props berechneter Stateno-derived-state
2State-Flag als Event-Auslöserno-event-handler
3Abgerufene Daten werden an einen Callback der Elternkomponente übergebenno-pass-data-to-parent
4State wird bei Prop-Änderung geleertno-adjust-state-on-prop-change (eine Warnung pro Setter)

Effect 4 ist der Fall, den key löst. no-reset-all-state-on-prop-change meldet dasselbe Muster in einfacheren Komponenten. In einer Komponente wie dieser, die ihren State während des Renderns liest, meldet Version 1.0.2 jedes Zurücksetzen jedoch stattdessen unter no-adjust-state-on-prop-change.

Der vollständige Regelsatz mit dem jeweiligen Ersatz:

RegelErsetzen Sie den Effect durch
no-derived-stateEinen während des Renderns berechneten Wert
no-chain-state-updatesEin zusammengefasstes State-Update
no-event-handlerCode im Event-Handler
no-adjust-state-on-prop-changeUmstrukturierten State oder eine Berechnung während des Renderns
no-reset-all-state-on-prop-changeDie als key übergebene Prop
no-pass-live-state-to-parentHochgezogenen State (Lifting State Up) oder einen Rückgabewert eines Hooks
no-pass-data-to-parentDatenabruf in der Elternkomponente, Ergebnis als Props übergeben
no-external-store-subscriptionuseSyncExternalStore
no-initialize-stateEinen Initialwert in useState()

Das Plugin verfolgt Aufrufketten und Dependency-Arrays und erhöht damit den Linting-Aufwand. Das Regel-Profiling von ESLint (TIMING=1) zeigt, was das in Ihrem eigenen Repository kostet. Die README empfiehlt außerdem, react-hooks/exhaustive-deps zu aktivieren, da das Plugin von korrekten Dependency-Arrays ausgeht, sowie typescript-eslint/no-floating-promises, das ihm hilft, Aufrufe asynchroner Funktionen zu erkennen.

Abgeleiteter State: Während des Renderns berechnen

Wenn sich ein Wert aus den vorhandenen Props und dem vorhandenen State ermitteln lässt, berechnen Sie ihn während des Renderns. Halten Sie keine Kopie im State vor, die Sie per Effect synchronisieren. Die React-Dokumentation behandelt dies unter Updating state based on props or state.

// Before
const [total, setTotal] = useState(0);
useEffect(() => {
  setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));
}, [items]);

// After
const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);

Die Summe ist jetzt bereits beim ersten Render korrekt. Falls sich die Berechnung als aufwendig herausstellt, kapseln Sie sie in useMemo. Legen Sie sie nicht wieder im State ab.

Event-Handler-Logik: In den Handler verschieben

Ein Effect, der auf ein in einem Submit- oder Click-Handler gesetztes State-Flag wartet, ist ein verkappter Event-Handler. Führen Sie diese Logik direkt im Handler aus. Die Begründung finden Sie in Separating Events from Effects.

// Before
const [submitted, setSubmitted] = useState(false);
useEffect(() => {
  if (submitted) postOrder(customerId, items, note);
}, [submitted, customerId, items, note]);

// After
function handleSubmit(e) {
  e.preventDefault();
  postOrder(customerId, items, note);
}

Die „Vorher“-Version enthält einen echten Bug: Wird die Notiz nach dem Absenden bearbeitet, wird die Bestellung erneut abgeschickt. Seit Version 0.10.1 markiert no-event-handler auch Props, die auf diese Weise verwendet werden, und seit 0.10.2 meldet die Regel den Befund an der Verwendungsstelle statt an der Deklaration.

Datenabruf und Datenweitergabe nach oben

Eine Kindkomponente, die Daten abruft und per Effect an ihre Elternkomponente weiterreicht, sollte diese Aufgabe an die Elternkomponente, das Data Loading des Frameworks oder eine Data-Fetching-Bibliothek abgeben und das Ergebnis als Props erhalten. React beschreibt dieselbe Umkehrung unter Passing data to the parent.

// Before: child fetches, then pushes up
const history = useCustomerHistory(customerId);
useEffect(() => {
  onLoaded(history);
}, [history, onLoaded]);

// After: parent owns the data
function OrderPage({ customerId, items }) {
  const history = useCustomerHistory(customerId); // loader, library, or parent effect
  return <OrderPanel customerId={customerId} items={items} history={history} />;
}

Wenn Daten nur in eine Richtung fließen, ist klar, welche Komponente sie besitzt, und OrderPanel braucht die Callback-Prop onLoaded nicht mehr, nur um zu melden, was sie geladen hat.

State bei Prop-Änderung zurücksetzen: key verwenden

Um den gesamten State einer Komponente zurückzusetzen, wenn sich eine Prop wie eine Kunden-ID ändert, rendern Sie die Komponente mit dieser Prop als key, sodass React eine neue Instanz mountet. Das Muster ist unter Resetting all state when a prop changes dokumentiert.

// Before: Effect 4 inside OrderPanel
useEffect(() => {
  setNote("");
  setSubmitted(false);
}, [customerId]);

// After: delete the effect; in the parent
<OrderPanel key={customerId} customerId={customerId} items={items} history={history} />

no-adjust-state-on-prop-change zielt auf die partielle Variante davon ab, bei der sich nur ein Teil des States als Reaktion auf eine Prop ändert. Wie der obige Lint-Durchlauf zeigt, meldet die Regel aber auch Effect 4. Wenn nur ein Teil des States zurückgesetzt werden soll, besteht die Lösung in der Regel darin, den State umzustrukturieren oder den Wert während des Renderns zu berechnen.

False Positives und Effects, die bleiben

Effects, die eine Komponente mit etwas verbinden, das React nicht verwaltet (Timer, DOM-APIs, Nicht-React-Widgets, Netzwerkverbindungen), sind genau die Aufgabe, für die useEffect entwickelt wurde. Lassen Sie sie bestehen. Die meisten davon benötigen eine Cleanup-Funktion, wie Synchronizing with Effects erläutert. Verursacht eine DOM-Messung ein sichtbares Flackern, ist die Wahl zwischen useEffect und useLayoutEffect wichtiger als die Frage, ob der Effect bleiben soll.

Subscriptions auf einen externen Store sind die Ausnahme von der Regel, Synchronisierungs-Effects beizubehalten. Sie gehören in useSyncExternalStore, das während des Renderns einen Snapshot liest, statt ihn per Effect in den State zu kopieren. no-external-store-subscription markiert die Effect-basierte Variante.

Die statische Analyse von Effects hat Grenzfälle. Die React-eigene Regel set-state-in-effect warnt, wenn ein Effect einen State-Setter synchron aufruft, und in einem React-Issue zur Strenge von set-state-in-effect wird darüber diskutiert, wie streng sie Mount-Flags und Muster zur Client-Erkennung behandelt. Verstehen Sie Warnungen bei solchen Mustern als Anlass, den Code zu prüfen, nicht als endgültiges Urteil in die eine oder andere Richtung. Beide Plugins parallel zu betreiben ist sinnvoll: Die React-Regel deckt setState in Effects ab, und dieses Plugin ergänzt Callbacks an Elternkomponenten, verkettete Updates und Store-Subscriptions.

Bevor Sie eine Warnung unterdrücken, fragen Sie sich:

  1. Kommuniziert der Effect mit einem System außerhalb von React?
  2. Benötigt er ein Cleanup, wenn sich Dependencies ändern oder die Komponente unmountet wird?
  3. Würde das Verschieben in den Render oder einen Handler ein für Nutzer sichtbares Verhalten ändern?

Rechtfertigen die Antworten, den Effect beizubehalten, deaktivieren Sie die Regel für diese Zeile und dokumentieren den Grund mit der ---Beschreibungssyntax von ESLint:

// eslint-disable-next-line react-you-might-not-need-an-effect/no-initialize-state -- client-only value, avoids hydration mismatch

Dieses Präfix entspricht dem der Presets, von Oxlint und von Legacy-Konfigurationen. Verwenden Sie reactYouMightNotNeedAnEffect/ nur, wenn Sie das Plugin selbst unter diesem Schlüssel registriert haben.

Einführung im Projekt

Führen Sie eslint-plugin-react-you-might-not-need-an-effect zunächst mit recommended ein, damit die Befunde als Warnungen erscheinen und die CI weiterhin durchläuft. Arbeiten Sie sie Komponente für Komponente ab, indem Sie jeden Befund refaktorieren oder annotieren, und wechseln Sie zu strict, sobald die Anzahl der Warnungen null erreicht. So lassen neue unnötige Effects den Build fehlschlagen.

FAQs

Kann ich eslint-plugin-react-you-might-not-need-an-effect mit einer Legacy-Konfiguration (.eslintrc) verwenden?

Ja. Legacy-Projekte mit .eslintrc erweitern plugin:react-you-might-not-need-an-effect/legacy-recommended, um jede Regel als Warnung zu melden, oder plugin:react-you-might-not-need-an-effect/legacy-strict, um sie als Fehler zu melden. In Legacy-Konfigurationen verwenden Regel-IDs und Disable-Kommentare das Kebab-Case-Präfix react-you-might-not-need-an-effect/, also dasselbe Präfix wie die Flat-Config-Presets.

Wann sollte ich einen abgeleiteten Wert in useMemo kapseln, statt ihn bei jedem Render zu berechnen?

Verwenden Sie useMemo nur, wenn die Berechnung messbar langsam ist. Die React-Dokumentation empfiehlt, sie mit console.time zu messen und zu memoisieren, wenn die Gesamtdauer etwa 1 ms oder mehr beträgt. useMemo speichert das Ergebnis zwischen Renders im Cache, hält es aber aus dem State heraus, sodass es bereits beim ersten Render korrekt ist. Der React Compiler kann viele Berechnungen automatisch memoisieren, wodurch manuelle useMemo-Aufrufe für abgeleitete Werte seltener nötig sind.

Setzt eine Änderung des key einer Komponente auch den State ihrer Kindkomponenten zurück?

Ja. Wenn sich der key ändert, unmountet React die Komponente samt ihrem gesamten Teilbaum und mountet anschließend neue Instanzen. Jede Kindkomponente verliert ihren lokalen State, die DOM-Knoten werden neu erzeugt, und alle Effects führen ihr Cleanup aus und laufen dann erneut. Soll nur ein Teil des States zurückgesetzt werden, während der State der Kindkomponenten erhalten bleibt, passen Sie diesen State direkt an oder strukturieren ihn um, statt key zu verwenden.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

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