React: [ESLint] Feedback zur Fusselregel 'exhaustive-deps'

Erstellt am 21. Feb. 2019  ·  111Kommentare  ·  Quelle: facebook/react

Häufige Antworten

💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡

Wir haben die Kommentare zu diesem Beitrag analysiert, um eine Anleitung zu geben: https://github.com/facebook/react/issues/14920#issuecomment -471070149.

💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡


Was ist das

Dies ist eine neue ESLint-Regel, die die Liste der Abhängigkeiten für Hooks wie useEffect und ähnliches verifiziert und so vor den veralteten Fallstricken schützt. In den meisten Fällen hat es einen Autofix. Wir werden in den nächsten Wochen weitere Dokumentation hinzufügen.

autofix demo

Installation

yarn add eslint-plugin-react-hooks<strong i="18">@next</strong>
# or
npm install eslint-plugin-react-hooks<strong i="19">@next</strong>

ESLint-Konfiguration:

{
  "plugins": ["react-hooks"],
  // ...
  "rules": {
    "react-hooks/rules-of-hooks": 'error',
    "react-hooks/exhaustive-deps": 'warn' // <--- THIS IS THE NEW RULE
  }
}

Einfacher Testfall, um zu überprüfen, ob die Regel funktioniert:

function Foo(props) {
  useEffect(() => {
    console.log(props.name);
  }, []); // <-- should error and offer autofix to [props.name]
}

Die Fusselregel beschwert sich, aber mein Code ist in Ordnung!

Wenn diese neue react-hooks/exhaustive-deps Lint-Regel für Sie ausgelöst wird, Sie aber der Meinung sind, dass Ihr Code korrekt ist , posten Sie dies bitte in dieser Ausgabe.


BEVOR SIE EINEN KOMMENTAR POSTEN

Bitte fügen Sie diese drei Dinge hinzu:

  1. Eine CodeSandbox, die ein minimales Codebeispiel demonstriert, das immer noch Ihre Absicht ausdrückt (nicht "foo bar", sondern das tatsächliche UI-Muster, das Sie implementieren).
  2. Eine Erklärung der Schritte, die ein Benutzer ausführt und was Sie auf dem Bildschirm erwarten.
  3. Eine Erläuterung der beabsichtigten API Ihres Hooks/Ihrer Komponente.

please

Aber mein Fall ist einfach, ich möchte diese Dinge nicht einbeziehen!

Für Sie mag es einfach sein – für uns ist es gar nicht so einfach. Wenn Ihr Kommentar keines von beiden enthält (zB kein CodeSandbox-Link), werden wir Ihren Kommentar ausblenden, da es sonst sehr schwer ist, die Diskussion zu verfolgen. Vielen Dank, dass Sie die Zeit aller respektieren, indem Sie sie einbeziehen.

Das Endziel dieses Threads ist es, gängige Szenarien zu finden und sie in bessere Dokumente und Warnungen umzuwandeln. Dies kann nur geschehen, wenn genügend Details vorliegen. Drive-by-Kommentare mit unvollständigen Code-Schnipseln reduzieren die Qualität der Diskussion erheblich – bis zu dem Punkt, dass es sich nicht lohnt.

ESLint Rules Discussion

Hilfreichster Kommentar

Wir haben diese heute mit @threepointone überflogen . Hier ist eine Zusammenfassung:

In der Fusselregel behoben

Überflüssige useEffect Abhängigkeiten

Die Regel hindert Sie nicht mehr daran, "fremde" Deps zu useEffect hinzuzufügen, da es legitime Szenarien gibt.

Funktionen in derselben Komponente, aber außerhalb des Effekts definiert

Linter warnt nicht für Fälle, in denen es jetzt sicher ist, aber in allen anderen Fällen gibt es Ihnen bessere Vorschläge (z. B. das Verschieben der Funktion innerhalb des Effekts oder das Umschließen mit useCallback ).

Behebung im Benutzercode wert

Zurücksetzen des Status bei Requisitenänderung

Dies erzeugt keine Lint-Verletzungen mehr, aber die idiomatische Art und Weise, den Status als Reaktion auf Requisiten zurückzusetzen, ist anders . Diese Lösung hat ein zusätzliches inkonsistentes Rendering, daher bin ich nicht sicher, ob dies wünschenswert ist.

"Mein Nicht-Funktionswert ist konstant"

Hooks stoßen Sie wo immer möglich zur Korrektheit. Wenn Sie die deps tun angeben (die in einigen Fällen können Sie weglassen), empfehlen wir dringend , auch diejenigen sind , dass Sie nicht ändern denken. Ja, in diesem useDebounce Beispiel wird sich die Verzögerung wahrscheinlich nicht ändern. Aber es ist immer noch ein Fehler, wenn dies der Fall ist, aber der Hook kann damit nicht umgehen. Dies zeigt sich auch in anderen Szenarien. (zB Hooks sind viel besser mit Hot Reloading kompatibel, da jeder Wert als dynamisch behandelt wird.)

Wenn Sie unbedingt darauf bestehen, dass ein bestimmter Wert statisch ist, können Sie ihn erzwingen.
Am sichersten tun Sie dies explizit in Ihrer API:

const useFetch = createFetch({ /* config object */});
const useDebounce = createDebounce(500);
const FormInput = createInput({ rules: [emailValidator, phoneValidator] });

Dann kann es sich eindeutig nicht ändern, es sei denn, Sie legen es in Rendering. (Was keine idiomatische Verwendung Ihres Hooks wäre.) Aber zu sagen, dass sich <Slider min={50} /> niemals ändern kann, ist nicht wirklich gültig – jemand könnte es leicht in <Slider min={state ? 50 : 100} /> ändern. Tatsächlich könnte jemand dies tun:

let slider
if (isCelsius) {
  slider = <Slider min={0} max={100} />
} else {
  slider = <Slider min={32} max={212} />
}

Wenn jemand isCelsius in den Zustand wechselt, wird eine Komponente, die davon ausgeht, dass sich min nie ändert, nicht aktualisiert. Es ist in diesem Fall nicht offensichtlich, dass Slider derselbe ist (aber es liegt daran, dass er dieselbe Position im Baum hat). Dies ist also eine wichtige Waffe, wenn es darum geht, Änderungen am Code vorzunehmen. Ein wichtiger Punkt von React ist, dass Updates genau wie die Anfangszustände gerendert werden (Sie können normalerweise nicht sagen, was welcher ist). Egal, ob Sie den Requisitenwert B rendern oder von Requisitenwert A zu B wechseln – es sollte gleich aussehen und sich verhalten.

Obwohl dies nicht ratsam ist, könnte der Durchsetzungsmechanismus in einigen Fällen ein Hook sein, der warnt, wenn sich der Wert ändert (aber den ersten liefert). Dann fällt es zumindest eher auf.

function useMyHook(a) {
  const initialA = usePossiblyStaleValue(a);
  // ...
}

function usePossiblyStaleValue(value) {
  const ref = useRef(value);

  if (process.env.NODE_ENV !== 'production') {
    if (ref.current !== value) { // Or your custom comparison logic
      console.error(
        'Unlike normally in React, it is not supported ' +
        'to pass dynamic values to useMyHook(). Sorry!'
      );
    }
  }

  return ref.current;
}

Es kann auch einen legitimen Fall geben, in dem Sie mit einem Update einfach nicht umgehen können. Zum Beispiel, wenn die API der unteren Ebene dies nicht unterstützt, wie ein jQuery-Plugin oder eine DOM-API. In diesem Fall ist eine Warnung dennoch angebracht, damit der Verbraucher Ihrer Komponente sie versteht. Alternativ können Sie eine Wrapper-Komponente erstellen, die key bei inkompatiblen Updates zurücksetzt – wodurch ein sauberes Remounten mit neuen Requisiten erzwungen wird. Dies ist wahrscheinlich für Blattkomponenten wie Schieberegler oder Kontrollkästchen vorzuziehen.

"Mein Funktionswert ist konstant"

Zuallererst, wenn es konstant ist und auf höchstes Niveau gehoben wird, wird sich der Linter nicht beschweren. Aber das hilft nicht bei Dingen, die aus Requisiten oder Kontext kommen.

Wenn es wirklich konstant ist dann in deps Angabe nicht verletzt. Zum Beispiel in dem Fall, in dem eine setState Funktion innerhalb eines benutzerdefinierten Hooks an Ihre Komponente zurückgegeben wird und Sie sie dann von einem Effekt aus aufrufen. Die Lint-Regel ist nicht schlau genug, um solche Indirektionen zu verstehen. Aber auf der anderen Seite kann jeder diesen Rückruf später vor der Rückkehr einschließen und möglicherweise auf eine andere Eigenschaft oder einen anderen Zustand darin verweisen. Dann wird es nicht konstant sein! Und wenn Sie diese Änderungen nicht handhaben, werden Sie böse veraltete Prop/State-Bugs haben. Es ist also eine bessere Standardeinstellung, es anzugeben.

Es ist jedoch ein Irrglaube, dass Funktionswerte notwendigerweise konstant sind. Sie sind in Klassen aufgrund der Methodenbindung häufiger konstant, obwohl dies eine eigene Reihe von Fehlern erzeugt . Aber im Allgemeinen kann jede Funktion, die über einem Wert in einer Funktionskomponente schließt, nicht als konstant angesehen werden. Die Fusselregel ist jetzt intelligenter, wenn es darum geht, Ihnen zu sagen, was zu tun ist. (Zum Beispiel das Verschieben in den Effekt – die einfachste Lösung – oder das Umschließen mit useCallback .)

Es gibt ein Problem im entgegengesetzten Spektrum davon, wo Sie Endlosschleifen erhalten (ein Funktionswert ändert sich immer). Wir fangen das jetzt, wenn möglich, in der lint-Regel ab (in derselben Komponente) und schlagen eine Lösung vor. Aber es ist knifflig, wenn Sie etwas mehrere Ebenen weitergeben.

Sie können es immer noch in useCallback umschließen, um das Problem zu beheben. Denken Sie daran, dass es technisch gesehen onChange={shouldHandle ? handleChange : null} oder foo ? <Page fetch={fetchComments /> : <Page fetch={fetchArticles /> an derselben Stelle rendern. Oder sogar fetchComments das über den Zustand der übergeordneten Komponente schließt. Das kann sich ändern. Bei Klassen ändert sich sein Verhalten im Hintergrund, aber die Funktionsreferenz bleibt gleich. Ihr Kind wird dieses Update also verpassen – Sie haben keine andere Möglichkeit, als dem Kind mehr Daten zu übermitteln. Bei Funktionskomponenten und useCallback ändert sich die Funktionsidentität selbst – aber nur bei Bedarf. Das ist also eine nützliche Eigenschaft und nicht nur ein Hindernis, das es zu vermeiden gilt.

Wir sollten eine bessere Lösung zum Erkennen von unendlichen asynchronen Schleifen hinzufügen. Das sollte den verwirrendsten Aspekt mildern. Wir könnten in Zukunft eine Erkennung dafür hinzufügen. Du könntest auch selbst so etwas schreiben:

useWarnAboutTooFrequentChanges([deps]);

Dies ist nicht ideal, und wir müssen darüber nachdenken, wie man dies eleganter handhabt. Ich stimme zu, dass Fälle wie dieser ziemlich unangenehm sind. Die Lösung ohne die Regel zu brechen wäre, rules statisch zu machen, zB indem man die API in createTextInput(rules) ändert und register und unregister in useCallback . Noch besser, entfernen Sie register und unregister und ersetzen Sie sie durch einen separaten Kontext, in dem Sie nur dispatch einfügen. Dann können Sie garantieren, dass Sie nie eine andere Funktionsidentität haben, als sie zu lesen.

Ich würde hinzufügen, dass Sie wahrscheinlich sowieso useMemo auf den Kontextwert setzen möchten, da der Anbieter viele Berechnungen durchführt, deren Wiederholung traurig wäre, wenn keine neuen Komponenten registriert, sondern seine eigenen Eltern aktualisiert würden. So wird das Problem, das Sie sonst vielleicht nicht bemerkt hätten, auf diese Weise sichtbarer. Obwohl ich zustimme, müssen wir es noch bekannter machen, wenn dies geschieht.

Das Ignorieren von Funktionsabhängigkeiten führt zu schlimmeren Fehlern bei Funktionskomponenten und Hooks, da sie andauernd veraltete Requisiten und Zustände sehen würden, wenn Sie dies tun. Versuchen Sie es also nicht, wenn Sie können.

Reagieren auf zusammengesetzte Wertänderungen

Für mich ist es seltsam, warum dieses Beispiel einen Effekt für etwas verwendet, das im Wesentlichen ein Ereignishandler ist. Dasselbe "Log" (ich nehme an, es könnte ein Formular-Senden sein) in einem Event-Handler scheint passender zu sein. Dies gilt insbesondere, wenn wir darüber nachdenken, was passiert, wenn eine Komponente ausgehängt wird. Was ist, wenn die Bereitstellung direkt nach der Planung des Effekts aufgehoben wird? Dinge wie das Absenden von Formularen sollten in diesem Fall nicht einfach "nicht passieren". Es scheint also, dass der Effekt dort eine falsche Wahl sein könnte.

Das heißt, Sie können immer noch tun, was Sie versucht haben – indem Sie fullName stattdessen zu setSubmittedData({firstName, lastName}) , und dann ist [submittedData] Ihre Abhängigkeit, aus der Sie firstName ablesen können. lastName .

Integration mit Imperative/Legacy-Code

Bei der Integration mit zwingenden Dingen wie jQuery-Plugins oder Raw-DOM-APIs kann mit einigen Unannehmlichkeiten gerechnet werden. Trotzdem würde ich erwarten, dass Sie die Effekte in diesem Beispiel etwas mehr konsolidieren können.


Ich hoffe, ich habe niemanden vergessen! Lassen Sie mich wissen, wenn ich es getan habe oder etwas unklar ist. Wir werden versuchen, die Lehren daraus in Kürze in einige Dokumente umzuwandeln.

Alle 111 Kommentare

Dieses Beispiel hat eine Antwort: https://github.com/facebook/react/issues/14920#issuecomment -466145690

CodeSandbox

Dies ist eine unkontrollierte Checkbox-Komponente, die eine defaultIndeterminate Requisite benötigt, um den unbestimmten Status beim anfänglichen Rendern festzulegen (was nur in JS mit einer Referenz möglich ist, da es kein indeterminate Elementattribut gibt). Diese Requisite soll sich wie defaultValue verhalten, wobei ihr Wert nur beim anfänglichen Rendern verwendet wird.

Die Regel beschwert sich, dass defaultIndeterminate im Abhängigkeitsarray fehlt, aber das Hinzufügen würde dazu führen, dass die Komponente den unkontrollierten Zustand fälschlicherweise überschreibt, wenn ein neuer Wert übergeben wird. Das Dependency-Array kann nicht vollständig entfernt werden, da dies dazu führen würde, dass der unbestimmte Status vollständig von der Requisite kontrolliert wird.

Ich sehe keine Möglichkeit, dies von der Art des Falles zu unterscheiden, den die Regel abfangen soll, aber es wäre großartig, wenn die endgültige Regeldokumentation eine vorgeschlagene Problemumgehung enthalten könnte. 🙂

Betreff: https://github.com/facebook/react/issues/14920#issuecomment -466144466

@billyjanitsch Würde das stattdessen funktionieren? https://codesandbox.io/s/jpx1pmy7ry

Ich habe useState für indeterminate hinzugefügt, das auf defaultIndeterminate initialisiert wird. Der Effekt akzeptiert dann [indeterminate] als Argument. Sie ändern es derzeit nicht – aber wenn Sie es später tun würden, würde das wohl auch funktionieren? So antizipiert der Code zukünftige mögliche Anwendungsfälle etwas besser.

Also habe ich diesen folgenden (Rand?) Fall bekommen, in dem ich etwas HTML übergebe und es mit dangerouslySetInnerHtml , um meine Komponente (einige redaktionelle Inhalte) zu aktualisieren.
Ich verwende nicht die html Requisite, sondern die Referenz, bei der ich ref.current.querySelectorAll , um etwas zu zaubern.
Jetzt muss ich html zu meinen Abhängigkeiten in useEffect hinzufügen, obwohl ich es nicht explizit verwende. Ist dies ein Anwendungsfall, bei dem ich die Regel eigentlich deaktivieren sollte?

Die Idee ist, alle Link-Klicks aus dem redaktionellen Inhalt abzufangen und einige Analysen zu verfolgen oder andere relevante Dinge zu tun.

Dies ist ein verwässertes Beispiel aus einer realen Komponente:
https://codesandbox.io/s/8njp0pm8v2

Ich verwende React-Redux, also wenn ich einen Action Creator in den Requisiten von mapDispatchToProps und diesen Action Creator in einem Hook verwende, erhalte ich eine exhaustive-deps Warnung.

Ich kann also natürlich die Redux-Aktion zum Abhängigkeitsarray hinzufügen, aber da die Redux-Aktion eine Funktion ist und sich nie ändert, ist dies unnötig, oder?

const onSubmit = React.useCallback(
  () => {
    props.onSubmit(emails);
  },
  [emails, props]
);

Ich erwarte, dass der Fussel die Deps in [emails, props.onSubmit] repariert, aber im Moment repariert er die Deps immer in [emails, props] .

  1. Eine CodeSandbox, die ein minimales Codebeispiel demonstriert, das immer noch Ihre Absicht ausdrückt (nicht "foo bar", sondern das tatsächliche UI-Muster, das Sie implementieren).

https://codesandbox.io/s/xpr69pllmz

  1. Eine Erklärung der Schritte, die ein Benutzer ausführt und was Sie auf dem Bildschirm erwarten.

Ein Benutzer fügt eine E-Mail hinzu und lädt diese E-Mail zur App ein. Ich entferne den Rest der Benutzeroberfläche absichtlich nur auf button da sie für mein Problem irrelevant sind.

  1. Eine Erläuterung der beabsichtigten API Ihres Hooks/Ihrer Komponente.

Meine Komponente hat eine einzelne Stütze, onSubmit: (emails: string[]) => void . Es wird mit dem Status emails aufgerufen, wenn ein Benutzer das Formular abschickt.


BEARBEITEN: Beantwortet in https://github.com/facebook/react/issues/14920#issuecomment -467494468

Dies liegt daran, dass props.foo() technisch gesehen props selbst als this an foo Aufruf weitergibt. foo könnte also implizit von props abhängen. Für diesen Fall brauchen wir jedoch eine bessere Nachricht. Die beste Vorgehensweise ist immer die Destrukturierung.

CodeSandbox

Es berücksichtigt nicht, dass sich Mount und Update bei der Integration von Bibliotheken von Drittanbietern deutlich unterscheiden können. Der Update-Effekt kann nicht im Mount-Effekt enthalten sein (und das Array vollständig entfernen), da die Instanz nicht bei jedem Rendern zerstört werden sollte

Hi,

Ich bin mir nicht sicher, was mit meinem Code hier nicht stimmt:

const [client, setClient] = useState(0);

useEffect(() => {
    getClient().then(client => setClient(client));
  }, ['client']);

Ich habe React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked

@joelmoss @sylvainbaronnet Ich ausgeblendet, weil sie nicht die Informationen enthielten, nach denen wir oben in der Ausgabe gefragt hatten. Das macht diese Diskussion für alle unnötig schwierig, weil der Kontext fehlt. Gerne führe ich das Gespräch fort, wenn Sie noch einmal posten und alle relevanten Informationen angeben (siehe Top-Beitrag). Danke für dein Verständnis.

  1. CodeSandbox
  2. Der Benutzer kann einen Vornamen auswählen und erzwingt die Änderung des Nachnamens. Der Benutzer kann einen Nachnamen auswählen und erzwingt keine Änderung des Vornamens.
  3. Es gibt einen benutzerdefinierten Hook, der einen Zustand zurückgibt, eine ausgewählte Komponente, die diesen Zustand aktualisiert, und die update-Methode des Zustands-Hooks, die den Zustand programmgesteuert aktualisiert. Wie gezeigt, möchte ich die Updater-Funktion nicht immer verwenden, also habe ich sie als letztes Element im zurückgegebenen Array belassen.

Ich glaube, der Code wie er ist, sollte nicht fehlerhaft sein. Das tut es jetzt in Zeile 35 und sagt, dass setLastName in das Array aufgenommen werden sollte. Irgendwelche Gedanken, was man dagegen tun kann? Mache ich etwas Unerwartetes?

Ich verstehe das vollkommen, und normalerweise würde ich das alles für Sie tun, aber in meinem speziellen Fall ist keiner der Codes für mich einzigartig. Es ist einfach eine Frage, ob die Verwendung einer Funktion, die außerhalb des Hooks definiert ist (z. B. ein Redux-Aktionsersteller) und innerhalb eines Hooks verwendet wird, erfordern sollte, dass diese Funktion als Hook dep hinzugefügt wird.

Gerne erstellen Sie eine Codesandbox, wenn Sie noch mehr Informationen benötigen. Danke

@joelmoss Ich denke, mein Repro deckt auch Ihren Fall ab.

Ja, eine CodeSandbox würde trotzdem helfen. Stellen Sie sich vor, wie es ist, den ganzen Tag zwischen den Code-Snippets von Personen zu wechseln. Es ist ein enormer mentaler Tribut. Keiner von ihnen sieht gleich aus. Wenn Sie sich daran erinnern müssen, wie die Leute Action-Ersteller in Redux oder einem anderen Konzept außerhalb von React verwenden, ist es noch schwieriger. Das Problem mag für Sie offensichtlich klingen, aber für mich ist es überhaupt nicht klar, was Sie meinten.

@gaearon Ich verstehe, dass es Sinn macht, eigentlich hat es für mich funktioniert, denn warum ich erreichen wollte, war:

Wenn Sie einen Effekt ausführen und nur einmal bereinigen möchten (beim Einhängen und Aushängen), können Sie als zweites Argument ein leeres Array ([]) übergeben.

Vielen Dank für die Aufklärung. (und sorry für das Off-Topic)

Hier ist die Codeversion dessen, was ich implementiere. Es sieht nicht nach viel aus, aber das Muster ist zu 100% mit meinem echten Code identisch.

https://codesandbox.io/s/2x4q9rzwmp?fontsize=14

  • Eine Erklärung der Schritte, die ein Benutzer ausführt und was Sie auf dem Bildschirm erwarten.

In diesem Beispiel funktioniert alles super! Außer das Linter-Problem.

  • Eine Erläuterung der beabsichtigten API Ihres Hooks/Ihrer Komponente.

Ich habe mehrere Dateien wie diese, in denen ich eine Funktion im Effekt ausführe, aber ich möchte, dass sie nur ausgeführt wird, wenn eine Bedingung erfüllt ist - zum Beispiel das Ändern der Serien-ID. Ich möchte die Funktion nicht in das Array aufnehmen.

Das bekomme ich vom Linter, wenn ich ihn im Sandbox-Code ausführe:

25:5   warning  React Hook useEffect has a missing dependency: 'fetchPodcastsFn'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

Meine Frage ist: sollte es sich so verhalten (entweder die Linter-Regel oder die Hook-Array-Regel)? Gibt es eine idiomatischere Art, diesen Effekt zu beschreiben?

@svenanders , ich bin neugierig auf den Grund, warum Sie fetchPodcastsFn nicht einschließen möchten? Liegt es daran, dass es sich bei jedem Rendering ändert? Wenn dies der Fall ist, möchten Sie diese Funktion wahrscheinlich auswendig lernen oder statisch machen (falls sie keine Parameter hat).

Zum einen geht es um Klarheit. Wenn ich mir die Funktion ansehe, möchte ich leicht verstehen, wann sie feuern soll. Wenn ich _one_ id im Array sehe, ist es kristallklar. Wenn ich diese ID und eine Reihe von Funktionen sehe, wird es unübersichtlicher. Ich muss Zeit und Mühe aufwenden, möglicherweise sogar Funktionen debuggen, um zu verstehen, was vor sich geht.
Meine Funktionen ändern sich zur Laufzeit nicht, daher weiß ich nicht, ob das Auswendiglernen von Bedeutung wäre (insbesondere diese Aktion ist eine Dispatch-Aktion, die ein Epos auslöst und schließlich zu einer Zustandsänderung führt).

https://codesandbox.io/s/4xym4yn9kx

  • Schritte

Der Benutzer greift auf eine Route auf der Seite zu, ist jedoch kein Superuser, daher möchten wir ihn von der Seite wegleiten. Das props.navigate wird über eine Router-Bibliothek eingefügt, daher möchten wir window.location.assign nicht wirklich verwenden, um ein Neuladen der Seite zu verhindern.

Funktioniert alles!

  • Beabsichtigte API

Ich habe die Abhängigkeiten richtig wie in der Code-Sandbox eingefügt, aber der Linter sagt mir, dass die Abhängigkeitsliste props anstelle von props.navigate . Warum ist das so?

Ein Tweet mit Screenshots! https://twitter.com/ferdaber/status/1098966716187582464

EDIT: ein triftiger Grund , diese fehlerhaft sein könnte , ist , wenn navigate() ist ein Verfahren , das auf einem gebundenen beruht this , wobei in diesem Fall technisch , wenn etwas innerhalb props ändert sich dann das Zeug innen this wird sich ebenfalls ändern.

CodeSandbox: https://codesandbox.io/s/711r1zmq50

Beabsichtigte API:

Mit diesem Haken können Sie jeden sich schnell ändernden Wert entprellen. Der entprellte Wert gibt nur dann den neuesten Wert wieder, wenn der useDebounce-Hook für den angegebenen Zeitraum nicht aufgerufen wurde. In Verbindung mit useEffect, wie wir es im Rezept tun, können Sie leicht sicherstellen, dass teure Operationen wie API-Aufrufe nicht zu häufig ausgeführt werden.

Schritte:

Das Beispiel ermöglicht es Ihnen, die Marvel Comic-API zu durchsuchen und verwendet useDebounce, um zu verhindern, dass API-Aufrufe bei jedem Tastendruck ausgelöst werden.

IMO das Hinzufügen von "alles", das wir im Abhängigkeits-Array verwenden, ist nicht effizient.

Betrachten Sie beispielsweise den useDebounce Hook . In der realen Welt (zumindest bei uns) ändert sich delay nach dem ersten Rendern nicht, aber wir prüfen bei jedem erneuten Rendern, ob es geändert wird oder nicht. In diesem Hook ist das zweite Argument von useEffect also besser [value] statt [value, delay] .

Bitte denken Sie nicht, dass flache Gleichstellungsprüfungen extrem billig sind. Sie können Ihrer App helfen, wenn sie strategisch platziert werden, aber nur jede einzelne Komponente rein zu machen, kann Ihre App tatsächlich langsamer machen. Kompromisse.

Ich denke, das Hinzufügen von allem im Dependencies-Array hat das gleiche (oder noch schlimmere) Leistungsproblem wie die Verwendung reiner Komponenten überall. Da es viele Szenarien gibt, in denen wir wissen, dass sich einige der von uns verwendeten Werte nicht ändern, sollten wir sie nicht in das Dependencies-Array einfügen , da, wie

Ich habe einige Rückmeldungen zur Aktivierung dieser Regel, um gemäß Konvention automatisch an benutzerdefinierten Hooks zu arbeiten.

Ich kann im Quellcode sehen, dass es eine Absicht gibt, den Leuten zu ermöglichen, eine Regex anzugeben, um benutzerdefinierte Hooks nach Namen zu erfassen:

https://github.com/facebook/react/blob/ba708fa79b3db6481b7886f9fdb6bb776d0c2fb9/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L490 -L492

Was würde das React-Team von einer zusätzlichen Namenskonvention für benutzerdefinierte Hooks mit Abhängigkeitsarrays halten? Hooks folgen bereits der Konvention, use voranzustellen, um von diesem Plugin als Hook erkannt zu werden. Die Konvention, die ich zum Erkennen von benutzerdefinierten Hooks vorschlagen würde, die von bestimmten Abhängigkeiten abhängig sind, wäre eine Art Postfix, etwa WithDeps , was bedeutet, dass ein vollständiger benutzerdefinierter Hook-Name etwa useCustomHookWithDeps . Das Postfix WithDeps würde dem Plugin mitteilen, dass das letzte Array-Argument eine von Abhängigkeiten ist.

Das Plugin könnte immer noch zusätzlich Regexes unterstützen, aber ich denke, ich würde einen großen Vorteil sehen, wenn es Bibliotheksautoren erlauben würde, ihre benutzerdefinierten Hooks einfach mit WithDeps zu exportieren, anstatt Bibliotheksnutzer zu zwingen, das Plugin explizit für alle benutzerdefinierten Einstellungen zu konfigurieren Haken von Drittanbietern oder anderweitig.

Es warnt und entfernt automatisch die benutzerdefinierte Gleichheitsprüfung.

const myEqualityCheck = a => a.includes('@');

useCallback(
  () => {
    // ...
  },
  [myEqualityCheck(a)]
);

Für useEffect denke ich nicht, dass die Warnung "unnötige Abhängigkeit" erscheinen sollte, da diese "Abhängigkeiten" die Auslösung des Effekts ändern.

Nehmen wir an, ich habe zwei Zähler, Eltern und Kind:

  • Die Zähler können unabhängig voneinander inkrementiert/dekrementiert werden.
  • Ich möchte den untergeordneten Zähler auf Null zurücksetzen, wenn sich der übergeordnete Zähler ändert.

Implementierung:

  const [parent, setParent] = useState(0);
  const [child, setChild] = useState(0);

  useEffect(
    () => {
      setChild(0);
    },
    [parent] // "unnecessary dependency: 'parent'"
  );

Die Erschöpfungs-Deps-Regel gibt die Warnung React Hook useEffect has an unnecessary dependency: 'parent'. Either exclude it or remove the dependency array.

screen shot 2019-02-25 at 11 06 40 am

Ich denke nicht, dass der Linter diesen Vorschlag machen sollte, da er das Verhalten der App ändert.

  1. CodeSandbox: https://codesandbox.io/s/ol6r9plkv5
  2. Schritte: Wenn sich parent ändert, wird child auf Null zurückgesetzt.
  3. Absicht: Anstatt die Abhängigkeit als unnötig zu kennzeichnen, sollte der Linter erkennen, dass parent das Verhalten von useEffect ändert und sollte mir nicht raten, sie zu entfernen.

[BEARBEITEN]

Als Workaround kann ich etwas schreiben wie

const [parent, setParent] = useState(0)
const [child, setChild] = useState(0)
const previousParent = useRef(parent)

useEffect(() => {
  if (parent !== previousParent.current) {
    setChild(0)
  }

  previousParent.current = parent
}, [parent])

Aber es gibt eine "Poesie" zum Original-Schnipsel, die mir gefällt.

Ich denke, die Frage dreht sich darum, ob wir das Abhängigkeitsarray nur als Mechanismus zur Leistungsoptimierung im Vergleich zum tatsächlichen Komponentenverhalten interpretieren sollten. Die Reaktion Hooks FAQ verwendet Leistung als ein Beispiel dafür , warum Sie es verwenden würden, aber ich habe interpretieren nicht das Beispiel meinen Sie nur die Abhängigkeit Array verwenden sollten , die Leistung zu verbessern, stattdessen sah ich es als eine bequeme Möglichkeit , die Wirkung Anrufe überspringen im Allgemeinen.

Es ist eigentlich ein Muster, das ich ein paar Mal verwendet habe, um einen internen Cache ungültig zu machen:

useEffect(() => {
  if (props.invalidateCache) {
    cache.clear()
  }
}, [props.invalidateCache])

Wenn ich es nicht auf diese Weise verwenden sollte, dann lass es mich einfach wissen und ignoriere diesen Kommentar.

@MrLeebo
Wie wäre es mit

  const [parent, setParent] = useState(0);
  const [child, setChild] = useState(0);
  const updateChild = React.useCallback(() => setChild(0), [parent]);

  useEffect(
    () => {
      updateChild();
    },
    [updateChild] 
  );

Ich würde diesen Ausschnitt von dir auch nicht verstehen. Die Abhängigkeit kann nur basierend auf Ihrem Code angenommen werden, aber es könnte sich um einen Fehler handeln. Während mein Vorschlag das nicht unbedingt löst, macht er es zumindest deutlicher.

Es sieht so aus, als ob dies zuvor über getDerivedStateFromProps gelöst wurde? Wie implementiere ich getDerivedStateFromProps? Hilfe?

@nghiepit Ich habe deinen Kommentar versteckt, weil du die erforderliche Checkliste im ersten Beitrag (zB CodeSandbox) ignoriert hast. Bitte folgen Sie der Checkliste und posten Sie erneut. Vielen Dank.

@eps1lon Sie hätten genau die gleiche Warnung bei useCallback , und ich stimme nicht zu, dass dieses Muster im Allgemeinen eine Verbesserung gegenüber dem Original darstellt, aber ich möchte das Thema nicht entgleisen, um darüber zu sprechen. Zur Verdeutlichung denke ich, dass die unnötige Abhängigkeitsregel speziell für useEffect oder useLayoutEffect gelockert werden sollte, da sie eine wirksame Logik enthalten können, aber die Regel sollte für useCallback beibehalten werden. useMemo usw.

Ich stoße auf einige der gleichen Probleme / Fragen zum mentalen Modell, die useEffect nicht so streng sein kann. Ich habe ein unglaublich erfundenes Beispiel, an dem ich für eine grundlegende Proof-of-Concept-Idee gearbeitet habe. Ich weiß, dass dieser Code scheiße ist und die Idee nicht besonders nützlich ist, aber ich denke, er veranschaulicht das vorliegende Problem gut. Ich denke, @MrLeebo hat die Frage, die ich habe, ziemlich gut ausgedrückt:

Ich denke, die Frage dreht sich darum, ob wir das Abhängigkeitsarray nur als Mechanismus zur Leistungsoptimierung im Vergleich zum tatsächlichen Komponentenverhalten interpretieren sollten. Die Reaktion Hooks FAQ verwendet Leistung als ein Beispiel dafür , warum Sie es verwenden würden, aber ich habe interpretieren nicht das Beispiel meinen Sie nur die Abhängigkeit Array verwenden sollten , die Leistung zu verbessern, stattdessen sah ich es als eine bequeme Möglichkeit , die Wirkung Anrufe überspringen im Allgemeinen.

https://codesandbox.io/s/5v9w81j244

Ich würde mir speziell den useEffect Hook in useDelayedItems ansehen. Im Moment führt das Einschließen der Eigenschaft items in das Abhängigkeitsarray zu einem Linting-Fehler, aber das Entfernen dieser Eigenschaft oder des Arrays führt zu einem Verhalten, das Sie nicht möchten.

Die Grundidee des useDelayedItems Hooks ist ein Array von Items und ein Config-Objekt (Verzögerung in ms, anfängliche Seitengröße). Nachdem config.delay vergangen ist, sind die Elemente nun der vollständige Satz von Elementen. Wenn ihm ein neuer Satz von Elementen übergeben wird, sollte der Hook diese "Verzögerungs"-Logik erneut ausführen. Die Idee ist eine sehr grobe und dumme Version des verzögerten Renderns großer Listen.

Danke für all die Arbeit an diesen Regeln, es war sehr hilfreich bei der Entwicklung. Auch wenn mein Beispiel von fragwürdiger Qualität ist, hoffe ich, dass es einen Einblick in die Verwendung dieser Operatoren gibt.

BEARBEITEN : Ich habe in der Codesandbox ein paar klärende Kommentare zur Verhaltensabsicht hinzugefügt. Ich hoffe, die Informationen sind ausreichend, aber lassen Sie es mich wissen, wenn noch etwas verwirrend ist.

Was ist mit einem einzelnen Wert, der andere Werte kombiniert?

Beispiel: fullName wird von firstName und lastName . Wir möchten den Effekt nur auslösen, wenn sich fullName ändert (wie wenn der Benutzer auf "Speichern" klickt), aber auch auf die Werte zugreifen möchten, die er im Effekt erstellt.

Demo

Das Hinzufügen von firstName oder lastName zu den Abhängigkeiten würde die Dinge zerstören, da wir den Effekt erst ausführen möchten, nachdem sich fullName geändert hat.

@aweary Ich bin mir nicht sicher, welchen Wert Sie aus der Indirektion useEffect Requisitenänderung erhalten. Es scheint, dass Ihr onClick mit diesem "Effekt"

https://codesandbox.io/s/0m4p3klpyw

Was einzelne Werte angeht, die andere Werte kombinieren, wird useMemo wahrscheinlich das sein, was Sie wollen. Die verzögerte Natur der Berechnung in Ihrem Beispiel bedeutet, dass sie nicht genau 1:1 mit Ihrem verknüpften Verhalten übereinstimmt.

Ich werde Codesandbox-Links für diese Beispiele erstellen und diesen Beitrag bearbeiten.

Ich habe eine extrem einfache Regel: Wenn sich die aktuelle Registerkarte geändert hat, scrollen Sie nach oben:
https://codesandbox.io/s/m4nzvryrxj

useEffect(() => {
    window.scrollTo(0, 0);
  }, [activeTab]);

Und ich habe React Hook useEffect has an unnecessary dependency: 'activeTab'. Either exclude it or remove the dependency array

Außerdem möchte ich beim Migrieren einiger Komponenten das Verhalten von componentDidMount replizieren:

useEffect(() => {
    ...
  }, []);

diese Regel beschwert sich stark darüber. Ich zögere, dem useEffect-Array jede Abhängigkeit hinzuzufügen.

Schließlich, wenn Sie eine neue Inline-Funktion vor useEffect deklarieren, wie folgt:
https://codesandbox.io/s/nr7wz8qp7l

const foo = () => {...};
useEffect(() => {
    foo();
  }, []);

Sie erhalten: React Hook useEffect has a missing dependency: 'foo'. Either include it or remove the dependency array
Ich bin mir nicht sicher, wie ich mich beim Hinzufügen von foo zur Liste der Abhängigkeiten fühle. In jedem Render-Foo ist eine neue Funktion und useEffect würde immer ausgeführt werden?

@ksaldana1 Es reicht nicht aus , den Nebeneffekt in den Ereignishandler

Es funktioniert auch nicht, wenn useReducer da der aktualisierte Status nicht im Ereignishandler verfügbar ist.

Was einzelne Werte angeht, die andere Werte kombinieren, wird useMemo wahrscheinlich das sein, was Sie wollen.

Wenn in diesem Beispiel useMemo würde, würde es brechen, weil useMemo fullName jedes Mal, wenn sich firstName oder lastName ändern, ein neues fullName ableiten würde. Das Verhalten hier ist, dass fullName nicht aktualisiert wird, bis auf die Schaltfläche Speichern geklickt wird.

@aweary Würde so etwas funktionieren? https://codesandbox.io/s/lxjm02j50m
Ich bin sehr gespannt, was die empfohlene Implementierung ist.

Ich bin auch neugierig, mehr über ein paar Dinge zu erfahren, die Sie gesagt haben. Können Sie mir dazu weitere Informationen geben?

Triggerung und Wirkung im Handler:

Dies würde dazu führen, dass der Nebeneffekt auftritt, bevor das eigentliche Update festgeschrieben wird, was dazu führen kann, dass der Nebeneffekt häufiger als gewünscht ausgelöst wird.

Verwenden des Zustands useReducer im Ereignishandler:

der aktualisierte Status ist nicht verfügbar.

Vielen Dank!

@bugzpodder irgendwie nicht verwandt, aber der Scroll-Aufruf sollte innerhalb von useLayoutEffect statt useEffect . Aktuell gibt es ein deutliches Flackern bei der Navigation zur neuen Route

Ich bin mir nicht sicher, ob dies beabsichtigt ist oder nicht:

Wenn Sie eine Funktion aus props aufrufen, schlägt der Linter vor, das gesamte props-Objekt als Abhängigkeit hinzuzufügen.

Kurzversion:

useEffect(function() {
  props.setLoading(true)
}, [props.setLoading])

// ⚠️ React Hook useEffect has a missing dependency: 'props'.

Vollversion: Codesandbox

Versuche es noch einmal... aber diesmal einfacher ;)

Wenn ich eine Funktion in den Requisiten oder von überall her übergebe und diese Funktion innerhalb eines Hooks verwende, erhalte ich eine exhaustive-deps Warnung.

Ich kann die Funktion also offensichtlich dem Abhängigkeitsarray hinzufügen, aber da es sich um eine Funktion handelt und sich nie ändert, ist dies unnötig, oder?

-> Sandkasten

Ich hoffe, das ist alles, was Sie brauchen, aber ich habe einfach die Sandbox von @siddharthkp gegabelt, da dies gezeigt hat, was ich meinte.

Danke.

Ich kann die Funktion also offensichtlich dem Abhängigkeitsarray hinzufügen, aber da es sich um eine Funktion handelt und sich nie ändert, ist dies unnötig, oder?

Das ist nicht richtig; Funktionen können sich ständig ändern, wenn das übergeordnete Element erneut rendert.

@siddharthkp

Wenn Sie eine Funktion aus props aufrufen, schlägt der Linter vor, das gesamte props-Objekt als Abhängigkeit hinzuzufügen.

Dies liegt daran, dass props.foo() technisch gesehen props selbst als this an foo übergibt. foo könnte also implizit von props abhängen. Für diesen Fall brauchen wir jedoch eine bessere Nachricht. Die beste Vorgehensweise ist immer die Destrukturierung.

Funktionen können sich ständig ändern, wenn das übergeordnete Element erneut rendert.

Sicher, aber wenn die Funktion an anderer Stelle und nicht von einer übergeordneten Komponente definiert würde, würde sie sich nie ändern.

Sicher, aber wenn die Funktion an anderer Stelle und nicht von einer übergeordneten Komponente definiert würde, würde sie sich nie ändern.

Wenn Sie es direkt importieren, werden Sie von der Lint-Regel nicht aufgefordert, es zu den Effektabhängen hinzuzufügen. Nur wenn es im Renderbereich liegt. Wenn es sich im Renderbereich befindet, weiß die Lint-Regel nicht, woher es kommt. Auch wenn es heute nicht dynamisch ist, könnte es morgen sein, wenn jemand die übergeordnete Komponente ändert. Es ist also die richtige Standardeinstellung. Es schadet nicht, es anzugeben, wenn es sowieso statisch ist.

thx @gaearon

Dies liegt daran, dass props.foo() technisch gesehen props selbst als this an foo übergibt. foo könnte also implizit von props abhängen. Für diesen Fall brauchen wir jedoch eine bessere Nachricht. Die beste Vorgehensweise ist immer die Destrukturierung.

Das beantwortet auch meine Frage. Dankeschön! 😄

https://codesandbox.io/s/98z62jkyro

Also erstelle ich eine Bibliothek für die Handhabung der Eingabevalidierung, indem ich alle Eingaben mit einer API registriere, die in einem Kontext bereitgestellt wird, damit sich jede Eingabe selbst registriert. Ich habe einen benutzerdefinierten Hook namens useRegister erstellt.

Ex:

 useEffect(
    () => {
      register(props.name, props.rules || []);
      console.log("register");
      return function cleanup() {
        console.log("cleanup");
        unregister(props.name);
      };
    },
    [props.name, props.rules, register, unregister]
  );

Wenn props.rules gezwungen wird, Teil der Abhängigkeiten zu sein, scheint es in einer unendlichen Render-Schleife zu enden. Props.rules ist ein Array von Funktionen, die als Validatoren registriert werden müssen. Ich habe dieses Problem nur bei der Bereitstellung von Arrays als Abhängigkeiten festgestellt. In meiner Codesandbox können Sie sehen, dass es beim Öffnen der Konsole eine Schleife gibt.

Nur mit props.name als Abhängigkeit funktioniert es wie beabsichtigt. Das Erzwingen der Abhängigkeiten wird, wie bereits erwähnt, das Verhalten der Anwendung ändern, und in diesem Fall sind die Nebenwirkungen schwerwiegend.

@bugzpodder

Betreff: https://github.com/facebook/react/issues/14920#issuecomment -467212561

useEffect(() => {
    window.scrollTo(0, 0);
  }, [activeTab]);

Dies scheint ein legitimer Fall zu sein. Ich werde die Warnung lockern, um externe Deps nur für Effekte zuzulassen. (Aber nicht für useMemo oder useCallback .)

Außerdem möchte ich beim Migrieren einiger Komponenten das Verhalten von componentDidMount replizieren:
diese Regel beschwert sich stark darüber. Ich zögere, dem useEffect-Array jede Abhängigkeit hinzuzufügen.

Entschuldigung, Sie haben dafür kein Beispiel hinzugefügt, daher haben wir die Gelegenheit verloren, darüber zu diskutieren. Genau aus diesem Grund bittet der OP-Beitrag darum , ein konkretes UI-Beispiel anzugeben . Das hast du für den ersten Punkt getan, aber nicht für diesen. Ich bespreche es gerne, wenn Sie ein konkretes Beispiel dafür hinzufügen. Die Besonderheiten hängen wirklich davon ab.

Schließlich, wenn Sie vor useEffect eine neue Inline-Funktion deklarieren, wie folgt: https://codesandbox.io/s/nr7wz8qp7l

In diesem Fall besteht die einfache Lösung darin, doSomething in den Effekt zu verschieben. Dann brauchen Sie es nicht zu deklarieren. Alternativ können Sie useCallback um doSomething herum

Ich weiß nicht, ob dies eine Funktionsanfrage für eine neue Regel ist oder etwas, das um exhaustive-deps verbessert werden könnte …

import React from 'react'
import emitter from './thing'

export const Foo = () => {
  const onChange = () => {
    console.log('Thing changed')
  }

  React.useEffect(() => {
    emitter.on('change', onChange)
    return () => emitter.off('change', onChange)
  }, [onChange])

  return <div>Whatever</div>
}

Da die Funktion onChange jedem Rendern erstellt wird, ist das Argument useEffect Hook [onChange] überflüssig und könnte genauso gut entfernt werden:

   React.useEffect(() => {
     emitter.on('change', onChange)
     return () => emitter.off('change', onChange)
-  }, [onChange])
+  })

Der Linter könnte dies erkennen und Ihnen empfehlen, das Array-Argument zu löschen.

Es gab Situationen, in denen ich eine Liste von Array-Elementen verwaltete, nur um zu erkennen, dass eines oder mehrere davon erstellt wurden und den Hook sowieso bei jedem Rendern ungültig machten.

Gerade veröffentlicht [email protected] mit ein paar Korrekturen und besseren Nachrichten für diese Regel. Nichts bahnbrechendes, aber ein paar Fälle sollten gelöst werden. Den Rest schaue ich mir nächste Woche an.

Ich habe auch den ersten möglichen Schritt zum Weglassen von "sicheren" Funktionsdeps hier gepostet: https://github.com/facebook/react/pull/14996. (Siehe Tests.) Wenn Sie Ideen zu nützlichen Heuristiken haben und Leute zu richtigen Fixes führen können, kommentieren Sie bitte den PR.

@gaearon Ausgezeichnete Idee. Dies wird auf jeden Fall nützlich sein, um einen besseren Stil bei der Verwendung von Haken zu haben 🙏

@gaearon Es funktioniert immer noch nicht, falls die Aktion von prop kommt. Betrachten Sie dieses Beispiel:

image

Dabei ist setScrollTop eine Redux-Aktion.

In diesem Beispiel in der Slider Komponente verwende ich useEffect , um zu warten, bis das DOM verfügbar ist, damit ich die noUiSlider-Komponente mounten kann. Ich übergebe daher [sliderElement] um sicherzustellen, dass die Ref im DOM verfügbar ist, wenn der Effekt ausgeführt wird. Wir rendern unsere Komponenten auch auf Servern, so dass auch das DOM vor dem Rendern verfügbar ist. Die anderen Requisiten, die ich in useEffect (dh min, max, onUpdate usw.) sind Konstanten und daher sehe ich keine Notwendigkeit, sie an den Effekt weiterzugeben.

screen shot 2019-03-02 at 5 17 09 pm


Hier ist der Effekt, wie er in Codesandbox zu sehen ist:

const { min, max, step } = props;
const sliderElement = useRef();

useEffect(() => {
  if (!sliderElement.current) {
    return;
  }

  const slider = sliderElement.current;
  noUiSlider.create(slider, {
    connect: true,
    start: [min, max],
    step,
    range: {
      min: [min],
      max: [max],
    },
  });

  if (props.onUpdate) {
    slider.noUiSlider.on('update', props.onUpdate);
  }

  if (props.onChange) {
    slider.noUiSlider.on('change', props.onChange);
  }
}, [sliderElement]);

@WebDeg-Brian Ich kann Ihnen ohne eine vollständige CodeSandbox-Demo nicht helfen. Es tut uns leid. Siehe den oberen Beitrag.

Ich habe ein wenig über das häufige Missverständnis „Funktionen ändern sich nie“ gepostet:

https://overreacted.io/how-are-function-components-different-from-classes/

Nicht ganz das gleiche Thema, aber relevant für diese Regel.

Hallo @gaearon , Hier ist das Beispiel, das Sie mich gebeten haben, hier zu posten (vom Tweeter) :)

Grundsätzlich versuche ich, meine Bibliothek zu konvertieren reagieren Falle zu Haken.
Dies ist nur eine Falle für Ereignisse, außerhalb / innerhalb eines Elements.

Mein Problem ist, dass, wenn useEffect nicht vom Zustandswert ( trapped ) abhängt, es manchmal veraltet ist.
Ich habe einige Kommentare und Protokolle geschrieben, um dies zu demonstrieren. Sehen Sie sich die Datei useTrap.js , die Kommentare und Protokolle befinden sich in den Funktionen useEffect und preventDefaultHelper .

Wenn ein Wert nicht innerhalb von useEffect , sollte er meines Wissens nicht Teil seiner Abhängigkeiten sein (korrigiere mich, wenn ich falsch liege).

  1. Eine CodeSandbox
  2. Schritte:
    Ein Benutzer klickt in das Feld, um es zu aktivieren, und außerhalb, um es zu deaktivieren, kann der Benutzer auch mit der rechten Maustaste klicken, obwohl es beim ersten Klick nicht das Kontextmenü auslösen sollte ( e.preventDefault ).
    Wenn ich "erster Klick" sage, meine ich den ersten Klick, der den Status ändert.
    Bei einer aktiven Box ändert ein Rechtsklick außerhalb der Box den Status auf "nicht aktiv" und verhindert das Kontextmenü. ein weiterer Klick außerhalb hat keinen Einfluss auf den Status, daher sollte das Kontextmenü erscheinen.

Ich hoffe, ich bin hier klar und der Anwendungsfall ist verständlich, bitte lassen Sie es mich wissen, wenn ich weitere Informationen bereitstellen muss. Vielen Dank!

Hallo @gaearon , ich

Es ist exemplarisch ausführlich und ich hoffe, ich kann es anschaulich und verständlich erklären.

Dies ist der aktuelle Stand davon: React-async-utils/src/hooks/useAsyncData.ts

Übersicht über die Funktionalität
Unterstützung des Benutzers beim Umgang mit asynchronen Anrufen, resultierenden Daten und deren Status während des Prozesses.

triggerAsyncData aktualisiert den asyncData Status asynchron gemäß einer getData Funktion, die ein Promise zurückgibt. triggerAsyncData kann sowohl als Effekt als auch "manuell" vom Hook-Benutzer aufgerufen werden.

Herausforderungen

  1. Die Abhängigkeiten des Effekts, der triggerAsyncData aufruft, sind die intrinsischen. triggerAsyncData ist eine Abhängigkeit des Effekts, wird aber bei jedem Rendering erstellt. Gedankengang bisher:

    1. Einfach als Abhängigkeit hinzufügen => Aber dann läuft der Effekt bei jedem Rendern.

    2. Fügen Sie es als Abhängigkeit hinzu, aber verwenden Sie useMemo / useCallback mit triggerAsyncData => useMemo / useCallback sollten nur für Leistungsoptimierungen verwendet werden SO VIEL ICH WEISS.

    3. Scope es innerhalb des Effekts => Dann kann ich es nicht an den Benutzer zurückgeben.

    4. Anstatt triggerAsyncData als Abhängigkeit zu verwenden, verwenden Sie die Abhängigkeiten von triggerAsyncData als Abhängigkeiten => Beste Option, die ich bisher gefunden habe. Aber es bricht die "erschöpfende Deps"-Regel.

  2. Jeder Eingabeparameter des benutzerdefinierten Hooks ist/wird eine Abhängigkeit von unserem inneren Effekt. Inline-Funktionen und Literalobjekte als Parameter lassen den Effekt also zu oft laufen.

    1. Überlassen Sie die Verantwortung dem Benutzer. Sie werden entsprechende Werte bereitstellen, mit useMemo / useCallback falls erforderlich => Ich fürchte, dies wird oft nicht der Fall sein. Und wenn sie es tun, ist es ziemlich ausführlich.

    2. Erlauben Sie dem benutzerdefinierten Hook ein zusätzliches Argument, um die Tiefen der Eingaben bereitzustellen, und verwenden Sie dieses anstelle der Eingaben selbst => Cool, weniger ausführlich, mehr Kontrolle für den Benutzer. Ich benutze das. Aber es bricht die "erschöpfende Deps"-Regel. (Eigentlich verwende ich dies und greife auf reguläre Deps zurück, wenn kein zusätzliches Argument bereitgestellt wird. Ich finde es ein starkes Muster).

  3. Schlecht verwaltete Abhängigkeiten für den benutzerdefinierten Hook erzeugen aufgrund des inneren Effekts eine unendliche asynchrone Schleife. Ich habe defensive Programmierung verwendet, um dies zu verhindern, aber es fügt eine "Fälschung" hinzu? Abhängigkeit ( asyncData ). Dies bricht erneut die "Exhaustive-Deps"-Regel.

Längere Erklärung als ich es mir wünschte ... aber ich denke, es spiegelt meine Bemühungen wider, Hooks richtig zu verwenden. Bitte lassen Sie mich wissen, wenn ich noch etwas tun kann, um diese Schwierigkeiten zu verdeutlichen.

Vielen Dank für all die Mühe hier!

Hey @gaearon, danke für all deine harte Arbeit.

  1. Ein minimales asynchrones Datenabrufbeispiel CodeSandbox- Beispiel.

  2. Es wird erwartet, dass der Benutzer 5 Lorem ipsum-Titelzeichenfolgen sieht, die von json api abgerufen werden.

  3. Ich habe einen benutzerdefinierten Hook zum Abrufen von Daten mit der beabsichtigten API erstellt:

const { data, isLoading, isError } = useDataApi('https://jsonplaceholder.typicode.com/posts')

Interna des benutzerdefinierten useDataApi Hooks:

...
  const fetchData = async () => {
    let response;
    setIsError(false);
    setIsLoading(true);

    try {
      response = await fetch(url).then(response => response.json());

      setData(response);
    } catch (error) {
      setIsError(true);
    }

    setIsLoading(false);
  };

  useEffect(
    () => {
      fetchData();
    },
    [url]
  );
...

Das Problem ist dieser Code

  useEffect(
    () => {
      fetchData();
    },
    [url]
  );

wobei react-hooks/exhaustive-deps eine Warnung auslöst, dass ich fetchData zu meinem Abhängigkeitsarray hinzufügen und url entfernen sollte.

Wenn ich diesen Haken in ändere

  useEffect(
    () => {
      fetchData();
    },
    [fetchData]
  );

dann feuert es ständig Anfragen ab und hört nie auf, was natürlich ein großes Problem ist. Ich bin mir nicht sicher, ob mein Code fehlerhaft ist oder react-hooks/exhaustive-deps ein falsch positives Ergebnis auslöst.

Jede Hilfe geschätzt. Vielen Dank.

PS Ich lese Ihre Kommentare über useEffect nicht für Daten passen zu Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects useEffect holen , jedoch reagieren docs behaupten , dass Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects , die mir das Vertrauen gegeben , dass die Daten useEffect groß ist für Abrufen von Daten. Also jetzt bin ich etwas verwirrt 😕

@jan-stehlik Sie sollten fetchData mit useCallback umschließen. Ich habe deine Codesandbox mit der notwendigen Änderung hier gegabelt https://codesandbox.io/s/pjmjxprp0m

Sehr hilfreich, vielen Dank @viankakrisna !

Wenn ein Wert nicht in useEffect enthalten ist, sollte er meines Wissens nicht Teil seiner Abhängigkeiten sein (korrigieren Sie mich, wenn ich falsch liege).

Ich glaube, du liegst hier falsch. Oder besser gesagt, ein bisschen verwirrt. Sie verwenden handleEvent in Ihrer Wirkung. Aber du deklarierst es nicht. Deshalb sind die Werte, die es liest, veraltet.

Interessant.

Sie verwenden handleEvent in Ihrem Effekt. Aber du deklarierst es nicht. Deshalb sind die Werte, die es liest, veraltet.

Was meinst du mit: _"Aber du deklarierst es nicht"_?
Es wird unter dem Effekt deklariert (wie alle anderen Handler).

Oder meinen Sie, weil der Handler den Zustandswert verwendet und der Effekt den Handler anhängt, bedeutet dies, dass der Effekt von diesem Zustandswert abhängt?

Oder meinen Sie, weil der Handler den Zustandswert verwendet und der Effekt den Handler anhängt, bedeutet dies, dass der Effekt von diesem Zustandswert abhängt?

Ja, wenn Sie eine Funktion verwenden , müssen Sie entweder erklären , es in deps (und in diesem Fall wickeln Sie es mit useCallback es neu zu erstellen vermeiden), oder alles , was der Funktion verwendet.

Ok, das ist mir neu! Danke für diesen Input @gaearon :)
Ich möchte nur, dass es für mich (und andere?)

Wenn ein Effekt eine Funktion aufruft, übergibt oder etwas mit ihr macht, müssen wir entweder an sein deps-Array übergeben:
Die Funktion selbst ODER Die Variablen, die diese Funktion verwendet.
Wenn die Funktion innerhalb der Funktionskomponente / des benutzerdefinierten Hooks deklariert ist, wird empfohlen, sie mit useCallback zu umschließen, damit sie nicht jedes Mal neu erstellt wird, wenn unsere Komponente oder unser benutzerdefinierter Hook ausgeführt wird.

Ich muss sagen, dass ich es in den Dokumenten nicht gesehen habe .
Glaubst du, es ist in Ordnung, es zum Abschnitt _Note_ hinzuzufügen?

Notiz
Das Array der Eingaben wird nicht als Argumente an die Effektfunktion übergeben. Konzeptionell stellen sie jedoch genau das dar: Jeder Wert, auf den innerhalb der Effektfunktion verwiesen wird, sollte auch im Eingabe-Array erscheinen. In Zukunft könnte ein ausreichend fortgeschrittener Compiler dieses Array automatisch erstellen.

Bearbeiten
Eine weitere Sache, in meinem Beispiel, was sind die Deps für useCallback wenn es handleEvent umschließt (oder aus diesem Grund andere Handler). ist es das event selbst?

Ich muss sagen, dass ich es in den Dokumenten nicht gesehen habe.

In der Dokumentation steht "jeder Wert, auf den in der Effektfunktion verwiesen wird, sollte auch im Eingabearray erscheinen". Auch Funktionen sind Werte. Ich stimme zu, dass wir dies besser dokumentieren müssen – darum geht es in diesem Thread :-) Ich sammle Anwendungsfälle für eine neue Dokumentationsseite, die diesem Thema gewidmet ist.

Eine weitere Sache, in meinem Beispiel, was sind die Deps für useCallback, wenn es handleEvent (oder aus diesem Grund andere Handler) umschließt. Ist es das Ereignis selbst?

Nicht sicher was du meinst. Es sind alle Werte, auf die von der Funktion außerhalb davon verwiesen wird. Genau wie in useEffect .

Ich denke, ich habe das mit Funktionen als Abhängigkeiten nicht durchdacht. Mein mentales Modell war falsch, ich dachte, ich würde eine Funktion nur dann als Abhängigkeit übergeben, wenn sie als Prop oder Argument an meine Komponente / meinen Hook geliefert wird. Danke, dass du mir das klargemacht hast.

Was das useCallback , habe ich es so verwendet:

const memoHandleEvent = useCallback(
    handleEvent
);

und natürlich memoHandleEvent als Abhängigkeit für useEffect auch an addEventListener anstatt der echten handleEvent Funktion. scheint zu funktionieren, hoffe, dass dies die richtige und idiomatische Vorgehensweise ist.

Beachten Sie, dass useCallback ohne zweites Argument nichts bewirkt.

Auch hier wäre eine komplette Sandbox notwendig. Ich kann anhand einer unvollständigen Beschreibung wie dieser nicht sagen, ob Ihr Fix korrekt ist.

Beachten Sie, dass useCallback ohne zweites Argument nichts tut.

Argg! :grimassieren: lol

Auch hier wäre eine komplette Sandbox notwendig. Ich kann anhand einer unvollständigen Beschreibung wie dieser nicht sagen, ob Ihr Fix korrekt ist.

Aw, das ist der gleiche Link von oben. Ich habe es gerade aktualisiert :)

Bitte aktualisiere die CodeSandbox-Links nicht: PI benötige sie wie sie ursprünglich waren – sonst kann ich die Lint-Regel nicht testen. Könnten Sie bitte zwei separate Sandboxen erstellen, wenn Sie zwei verschiedene Lösungen haben? So kann ich jeden prüfen.

Ups, tut mir Leid! :PI hat die Anzahl der Sandboxes auf meinem Konto überschritten. Lassen Sie mich einige löschen und ich werde eine andere erstellen (und die Änderungen im Original rückgängig machen).

@gaearon das ist der zweite Link zur Lösung mit useCallback

Ich habe ein Szenario, das meiner Meinung nach in Ordnung ist, aber der Linter beschwert sich. Meine Probe:

CodeSandbox

Das Beispiel versucht darzustellen, wenn ein Benutzer auf eine Schaltfläche klickt, dann wird eine Anforderung gestellt, neue Daten basierend auf einer zuvor bereitgestellten ID und einer Funktion anzufordern. Wenn sich die ID ändert, sollten keine neuen Daten angefordert werden; nur eine neue Anforderung zum erneuten Laden sollte eine neue Datenanforderung auslösen.

Das Beispiel hier ist ein wenig konstruiert. In meiner realen Anwendung lebt React in einem DIV, das ein kleiner Teil einer viel größeren Webanwendung ist. Die übergebene Funktion erfolgt über Redux und mapDispatchToProps, wobei eine Aktionserstellung die ID übernimmt und eine Ajax-Anfrage zum Abrufen von Daten und zum Aktualisieren des Speichers sendet. Die Requisite refreshRequest wird über React.createElement übergeben. In meiner ursprünglichen Implementierung hatte ich Code, der in der Klassenkomponente so aussah:

componentDidUpdate (prevProps) { const { getData, someId, refreshRequest} = this.props; if (prevProps.refreshRequest!== this.props.refreshRequest) { getData(someId); } }

Ich versuche, das gleiche Verhalten mit einem Effekt-Hook zu implementieren. Aber wie im Beispiel geschrieben, beschwert sich der Linter:

Warnung React Hook useEffect hat fehlende Abhängigkeiten: 'getData' und 'someId'. Fügen Sie sie entweder ein oder entfernen Sie das Abhängigkeitsarray

Wenn ich alles hinzufüge, was der Linter will, wird useEffect ausgelöst, wenn der Benutzer auf eine der Schaltflächen im Beispiel klickt. Aber ich möchte, dass es nur ausgelöst wird, wenn die Schaltfläche Neue Daten anfordern gedrückt wird.

Hoffentlich macht es Sinn. Ich erkläre gerne mehr, wenn etwas unklar ist. Dankeschön!

Ich habe gerade [email protected] das eine experimentelle Unterstützung für die Erkennung von Abhängigkeiten von bloßen Funktionen bietet (die ohne useCallback der Regel nicht sehr nützlich sind). Hier ist ein Gif:

demo

Ich würde mich freuen, wenn Sie es in Ihren Projekten ausprobieren und sehen könnten, wie es sich anfühlt! (Bitte kommentieren Sie diesen speziellen Flow in https://github.com/facebook/react/pull/15026.)

Ich werde es morgen mit Beispielen aus diesem Thread versuchen.

Ich habe es noch nicht ausprobiert, aber ich frage mich, ob es mit dem Heben zu tun hat. Dies ist nur ein "Minimalbeispiel", das ich mir ausgedacht habe, daher ist es für nichts nützlich, aber ich verwende häufig hochgezogene Deklarationen, um es einfacher zu machen, die Anweisung return .

function Component() {
  useEffect(() => {
    handleChange
  }, [handleChange])

  return null

  function handleChange() {}
}

Es wäre schön, wenn die Regel eine Option hätte, um eine andere Funktion so zu konfigurieren, dass sie sich in Bezug auf den Linter wie useEffect verhält. Zum Beispiel habe ich dies hinzugefügt, damit ich einfach asynchrone Funktionen für Effekte verwenden kann, die einen AJAX-Aufruf ausführen. Auf diese Weise verliere ich jedoch alle exhaustive-deps Linting-Vorteile:

const useAsyncEffect = (fn, ...args) => {
    /* eslint-disable react-hooks/exhaustive-deps */
    useEffect(() => {
        fn();
    }, ...args);
};

Bearbeiten: Egal, mir ist gerade aufgefallen, dass dies bereits mit der Option additionalHooks der Regel möglich ist.

Hallo zusammen,
Ich habe ein Beispiel https://codesandbox.io/s/znnmwxol7l

Folgendes möchte ich:

function App() {
  const [currentTime, setCurrentTime] = React.useState(moment());

  const currentMonth = React.useMemo(
    () => {
      console.log("RUN");
      return currentTime.format("MMMM");
    },
    [currentTime.format("MMMM")] // <= this proplem [currentTime]
  );

  return (
    <div className="App">
      <h1>Current month: {currentMonth}</h1>
      <div>
        <button
          onClick={() => setCurrentTime(currentTime.clone().add(1, "days"))}
        >
          + 1 day
        </button>
      </div>
      <div>{currentTime.toString()}</div>
    </div>
  );
}

Aber das bekomme ich:

  const currentMonth = React.useMemo(
    () => {
      console.log("RUN");
      return currentTime.format("MMMM");
    },
    [currentTime] // <= this proplem
  );

Jedes Mal, wenn sich currentTime ändert, wird currentMonth unnötig neu berechnet

Oder irgendwie kann ich es unten tun:

  const currentMonth = React.useMemo(
    () => {
      return currentTime.format("MMMM");
    },
    [myEqualityCheck(currentTime)]
  );

Sorry, falls das schon beantwortet wurde, konnte es im obigen Thread nicht sehen:
Um den useEffect-Hook nur beim Mounten auszuführen, definieren Sie als zweiten Parameter ein leeres Eingabearray. Wenn dies jedoch geschieht, werden die eingehenden Beschwerden über die Einbeziehung dieser Eingabeargumente erhoben, die den Effekt so ändern würden, dass er auch beim Update ausgeführt wird.
Was ist der Ansatz, um useEffect nur auf Mounts mit aktivierten Erschöpfungsdeps auszuführen?

@einarq Ich denke, Sie müssen sicherstellen, dass sich alle useEffect nie ändern. Das könnte mit anderen Hooks wie useMemo . Danach wird der Code nur einmal ausgeführt, unabhängig davon, ob diese ESlint-Regel alle Verweise in das Array (mit Autofix) hinzufügt oder nicht.

@einarq Wie an anderer Stelle im Thread erwähnt, können wir Ihnen ohne CodeSandbox nicht helfen. Denn die Antwort hängt wirklich von Ihrem Code ab.

@nghiepit Dein Beispiel macht für mich nicht wirklich Sinn. Wenn Ihre Eingabe currentTime.format("MMMM") dann optimiert useMemo nichts für Sie, da Sie es bereits berechnet haben . Sie berechnen es also nur unnötig zweimal.

Ist es möglich, anzugeben, welcher Argumentindex der Rückruf für die Option additionalHooks ? Ich sehe, dass wir jetzt im Code davon ausgehen, dass es das erste wäre https://github.com/facebook/react/blob/9b7e1d1389e080d19e71680bbbe979ec58fa7389/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L1051 - L1081

Aktuell nicht möglich. Unsere allgemeine Anleitung ist, dass benutzerdefinierte Hooks es vorziehen sollten, kein deps Argument zu haben, da es sehr schwierig wird, darüber nachzudenken, wie Deps komponieren. Wenn Sie eine Funktion verwenden, möchten Sie die Benutzer möglicherweise stattdessen zu useCallback auffordern.

@CarlosGines Können Sie bitte eine CodeSandbox erstellen, wie im OP-Beitrag angefordert. Ich frage dies aus einem bestimmten Grund – andernfalls fällt es mir schwer, Änderungen zu überprüfen. Vor allem, wenn es in TypeScript geschrieben ist.

Ich habe gerade [email protected] mit hoffentlich nützlicheren Nachrichten und intelligenteren Heuristiken veröffentlicht. Bitte probieren Sie es mit Ihren Projekten aus, bevor es stabil wird.

Ich erwarte, dass es bei den meisten Beispielen in diesem Thread vernünftige Ratschläge geben sollte, die Ihnen helfen sollten, die Probleme zu beheben.

sollte das [email protected] ?

Jawohl.

Eigentlich gerade [email protected] mit ein paar kleinen Änderungen veröffentlicht.

@gaearon Hast du es eslint in der Codesandbox zum Laufen zu bringen?

@einarq vielleicht würde so etwas funktionieren?

const useDidMount = fn => {
  const callbackFn = useCallback(fn)
  useEffect(() => {
    callbackFn()
  }, [callbackFn])
}

Es ist mir eigentlich peinlich zu sagen, dass ich das Plugin nicht zum Laufen bringen kann auf einer einfachen CRA-App (basierend auf meinem Snippet ).
Hier ist das gegabelte Repo von Codesandbox.

Ich habe diesen Hinweis in den Dokumenten gesehen :

Hinweis: Wenn Sie Create React App verwenden, warten Sie bitte auf eine entsprechende Version von React-Scripts, die diese Regel enthält, anstatt sie direkt hinzuzufügen.

Aber gibt es derzeit keine Möglichkeit, es mit CRA zu testen? 👂

Ja, Sie müssten es auswerfen und zur ESLint-Konfiguration hinzufügen, bis wir es dort hinzufügen. Sie können einen Zweig auswerfen und nicht zusammenführen. :-)

Hi,

Zunächst einmal: Vielen Dank für die enge Zusammenarbeit mit der Community und die großartige Arbeit, die Sie im Allgemeinen leisten!

Wir haben ein Problem mit einem benutzerdefinierten Hook, den wir um die Fetch API herum erstellt haben. Ich habe eine Codesandbox erstellt, um das Problem zu demonstrieren.

Beispiel für Codesandbox

https://codesandbox.io/s/kn0km7mzv

Hinweis: Das Problem (siehe unten) führt zu einer Endlosschleife und ich möchte nicht DDoS jsonplaceholder.typicode.com dem ich das Problem demonstriere. Daher habe ich einen einfachen Request-Limiter mit einem Zähler eingebaut. Dies ist nicht erforderlich, um das Problem zu demonstrieren, aber es fühlte sich falsch an, eine unbegrenzte Anzahl von Anfragen an dieses großartige Projekt zu senden.

Erläuterung

Die Idee ist, die Handhabung der 3 möglichen Zustände einer API-Anfrage zu vereinfachen: Laden, Erfolg und Fehler. Daher haben wir einen benutzerdefinierten Hook useFetch() der 3 Eigenschaften zurückgibt isLoading , response und error. It makes sure that either Antwort or Fehler is set and updates isLoading . As the name implies, it uses the Fetch` API.

Dazu verwendet es 3 useState Hooks:

  const [isLoading, setIsLoading] = useState(false);
  const [response, setResponse] = useState(null);
  const [error, setError] = useState(null);

und ein useEffect Hook:

useEffect(
    () => {
      fetch(url, fetchConfig)
        .then(/* Handle fetch response... See Codesandbox for the actual implementation */)
    },
    [url]
  );

Es funktioniert gut, solange das Dependencies-Array von useEffect nur [url] . Wenn wir fetchConfig (= [url, fetchConfig] ) hinzufügen, führt dies zu einer Endlosschleife. Für unseren speziellen Anwendungsfall würde es ausreichen, den Effekt nur dann erneut auszuführen, wenn sich url ändert, aber der Linter erlaubt es nicht, nur [url] (getestet mit v1.4.0 und v1.5.0-beta.1 ).

Am Ende des benutzerdefinierten Hooks werden die 3 Zustandsvariablen als Objekt zurückgegeben:

  return {
    error,
    isLoading,
    response
  };

Ich bin mir nicht sicher, ob dies der richtige Ort ist, um um Rat zu diesem Thema zu bitten, da der Linting-Vorschlag sinnvoll ist, denke ich. Tut mir leid, wenn nicht.

Was passiert, wenn sich fetchConfig ändert? Warum erwarten Sie, dass es statisch ist?

Gut. Ich habe [email protected] mit den Fixes und Verbesserungen der letzten Woche veröffentlicht.
Dieser Thread war immens hilfreich bei der Suche nach verschiedenen Mustern, die Probleme verursachen.

Vielen Dank an euch alle. (Vor allem diejenigen, die Sandboxen geliefert haben. :-)


Ich werde nicht auf jeden persönlich im Detail eingehen, da es viele Fälle sind.

Stattdessen werden wir in den nächsten Tagen gängige Rezepte und Fallstricke aufschreiben und aus den Dokumenten verlinken. Ich werde sicherstellen, dass jedes gängige Muster in diesem Thread behandelt wird (oder bitten Sie, in einer separaten Ausgabe für seltene Muster nachzufassen).

Sobald die Beispiele veröffentlicht sind, kommentiere ich hier und bearbeite jeden Kommentar, der eine CodeSandbox enthält, indem ich einen Link an die entsprechende Antwort / das entsprechende Beispiel anfüge, das die richtige Lösung demonstriert. Ich hoffe, dies hilft Ihnen und zukünftigen Lesern.

Danke schön!

@timkraut Sie sollten in der Lage sein, fetchConfig auf den Deps hinzuzufügen, und die Komponente sollte es mit einem Memo umschließen, damit die Referenz erhalten bleibt.
Ein Beispiel: https://codesandbox.io/s/9l015v2x4w


Mein Problem dabei ist, dass die Komponente jetzt die Implementierungsdetails des Hooks kennen muss ...

Ich bin mir nicht sicher, ob dieser Thread noch offen für Beispieldiskussionen ist, aber ich drehe mich immer noch um Best Practices. Meine Frage betrifft die Steuerung, wann der useEffect-Hook mit dem Abhängigkeitsarray ausgelöst wird, und die Verwendung der Werte zu diesem Zeitpunkt vs. die Deklaration eines separaten Refs, um die Lint-Regel zu umgehen.

Beispiel

https://codesandbox.io/s/40v54jnkyw

In diesem Beispiel versuche ich, Eingabewerte regelmäßig automatisch zu speichern.

Erläuterung

Alle 5 Sekunden versucht der Code, die aktuellen Werte automatisch zu speichern (drucken Sie sie vorerst einfach auf dem Bildschirm aus). Der Linter würde erfordern, dass alle Eingabewerte in das Abhängigkeitsarray aufgenommen werden, wodurch sich ändern würde, wie oft der Effekt ausgelöst wird.

  useEffect(
    () => {
      if (autoSaveTick % 5 === 0) {
        setAutosaveValue(
          `${input1Value.value}, ${input2Value.value}, ${input3Value.value}`
        );
      }
    },
    [autoSaveTick]
  );

Die Alternative, die ich sehe, ist eine Implementierung ähnlich den definierten useTick und useInterval Hooks im Beispiel, wo einem Ref ein Callback zugewiesen ist. Es scheint, als würde dies unnötige Neudefinitionen von Funktionen verursachen, nur um die Lint-Regel zu berücksichtigen.

Ich suche nach Best Practices beim Schreiben eines Effekts, der ausgeführt wird, wenn sich eine Variable ändert (Variable A), die andere Variablenwerte (Variable B und Variable C) zum Zeitpunkt der vorherigen Variablenänderung (Variable A) verwendet.

Was passiert, wenn sich fetchConfig ändert? Warum erwarten Sie, dass es statisch ist?

Ich wäre in Ordnung, es dem Dependencies-Array hinzuzufügen. In unserem speziellen Geschäftsfall kann dies nie passieren, aber ich denke, es ist trotzdem eine gute Idee, es hinzuzufügen.

Mein Problem dabei ist, dass die Komponente jetzt die Implementierungsdetails des Hooks kennen muss ...

Das ist genau das Problem, das wir auch haben :/ In unserer Implementierung haben wir sogar eine zusätzliche Eigenschaft verwendet, um es einfacher zu machen, den Körper zu setzen, sodass wir immer 2 useMemo() Aufrufe verwenden müssten, wenn wir diesen Hook verwenden. Ich bin mir noch nicht sicher, wie Sie diese Einschränkung überwinden können. Wenn Sie eine Idee haben, lassen Sie es mich wissen!

Wie können Sie einen useEffect ausführen, der nur einmal Abhängigkeiten hat, ohne dass sich die Regel beschwert, wenn ein leeres Array übergeben wird?

Sagen Sie etwas wie:

useEffect(() => { init({ dsn, environment}) }, [])

Mein Problem dabei ist, dass die Komponente jetzt die Implementierungsdetails des Hooks kennen muss ...

Ich würde nicht sagen, dass dies Implementierungsdetails sind. Es ist Ihre API. Wenn ein Objekt übergeben wird, gehen wir davon aus, dass es sich jederzeit ändern kann.

Wenn es in der Praxis immer statisch ist, können Sie es zu einem Argument für eine Hook-Fabrik machen. Wie createFetch(config) , das useFetch() zurückgibt. Sie rufen die Fabrik auf der obersten Ebene an.

Ich verstehe, dass es ein bisschen seltsam ist. Wir haben ein ähnliches Problem mit dem useSubscription , an dem wir arbeiten. Da es sich jedoch um ein häufiges Problem handelt, kann dies bedeuten, dass useMemo tatsächlich eine legitime Antwort ist und die Leute sich einfach daran gewöhnen sollten, es für diese Fälle zu tun.

In der Praxis – wir werden uns das in Zukunft genauer anschauen. Sie sollten ein potenziell dynamisches Objekt jedoch nicht als statisches Objekt behandeln, da ein Benutzer es dann nicht ändern kann.

@asylejmani Sie haben keine Angaben zu Ihren Anwendungsfällen gemacht, wie im obersten Beitrag gefordert. Warum erwarten Sie, dass jemand Ihre Frage beantworten kann?

Der Sinn der Regel besteht darin, Ihnen mitzuteilen, dass sich environment und dsn im Laufe der Zeit ändern können und Ihre Komponente damit umgehen sollte. Entweder ist Ihre Komponente fehlerhaft (weil sie Änderungen an diesen Werten nicht verarbeitet) oder Sie haben einen einzigartigen Fall, in dem dies keine Rolle spielt (in diesem Fall sollten Sie einen Kommentar zur Lint-Regel ignorieren, der erklärt, warum).

In beiden Fällen ist nicht klar, was Sie fragen. Die Regel beschwert sich, dass sich die Dinge ändern können und Sie diese Änderung nicht handhaben. Ohne ein vollständiges Beispiel können Sie allein beantworten, warum Sie denken, dass es nicht notwendig ist, damit umzugehen.

@gaearon Entschuldigung, dass

Ich hatte den Eindruck, dass ein leeres Array dies tut, aber die Regel sagt mir, dass ich immer Abhängigkeiten in das Array einbeziehen soll.

@asylejmani Ich denke, der größte Fallstrick bei Klassenlebenszyklusmethoden wie componentDidMount ist, dass wir dazu neigen, sie als isolierte Methode zu betrachten, aber tatsächlich ist sie Teil eines Flusses.
Wenn Sie auf etwas in componentDidMount verweisen, müssen Sie es höchstwahrscheinlich auch in componentDidUpdate , oder Ihre Komponente kann fehlerhaft werden.
Dies versucht die Regel zu beheben, Sie müssen mit Werten im Laufe der Zeit umgehen.

  1. Komponente montiert, etwas mit einer Stütze machen
  2. Komponente aktualisiert (zB: Prop-Wert hat sich geändert), mach etwas mit dem neuen Prop-Wert

Nummer 1 ist, wo Sie die Logik in componentDidMount / useEffect Körper einfügen
Nummer 2 ist, wo Sie die Logik in componentDidUpdate / useEffect deps . eingeben

Die Regel lautet, dass Sie sich beschweren, dass Sie den zweiten Teil des Flows nicht tun

@gaearon Entschuldigung, dass

Ich hatte den Eindruck, dass ein leeres Array dies tut, aber die Regel sagt mir, dass ich immer Abhängigkeiten in das Array einbeziehen soll.

Ich denke, ich und @asylejmani sind hier auf derselben Seite, aber ich denke, was Sie @gaearon sagen, ist, dass wir wahrscheinlich falsch liegen, den Effekt nur auf mount auszuführen, wenn wir tatsächlich Abhängigkeiten haben.
Ist das eine faire Aussage? Ich denke, dass die Bereitstellung eines leeren Arrays so etwas bedeutet, als würde ich sagen: "Ich weiß, was ich tue", aber ich verstehe, warum Sie die Regel immer noch haben möchten.

Sorry, dass ich noch keine Sandbox zur Verfügung gestellt habe. Ich habe neulich mit einem Create React App-Beispiel angefangen, konnte nicht herausfinden, wie man eslint auf der Sandbox ausführt, und habe dann die Sandbox beim Neuladen des Browsers verloren, ohne vorher zu speichern (angenommene CodeSandbox-Temp gespeichert, mein Fehler).
Dann musste ich ins Bett und hatte seitdem keine Zeit mehr.

Auf jeden Fall verstehe ich, was Sie sagen, und dass es unter normalen Szenarien wahrscheinlich am besten ist, diese Abhängigkeiten einzubeziehen und nicht davon auszugehen, dass es ausreicht, nur auf Mount ausgeführt zu werden.

Es gibt wahrscheinlich auch dafür gültige Anwendungsfälle, aber es ist ein bisschen schwer zu erklären oder ein gutes Beispiel zu finden, also werde ich damit leben, die Regel bei Bedarf inline zu deaktivieren.

@asylejmani ist Ihr Anwendungsfall ähnlich wie https://github.com/facebook/react/issues/14920#issuecomment -466378650? Ich glaube nicht, dass die Regel das Szenario in diesem Fall verstehen kann, daher müssen wir sie für diese Art von Code nur manuell deaktivieren. In allen anderen Fällen funktioniert die Regel wie sie soll.

Ich bin mir nicht sicher, ob das Sinn macht, aber ein Szenario, das für mich ziemlich häufig vorkommt, ist etwa dieses:

useEffect(() => {
    if(!dataIsLoaded) { // flag from redux
        loadMyData(); // redux action creator
    }
}, []);

Beide Abhängigkeiten kommen von Redux. Die Daten (in diesem Fall) sollten nur einmal geladen werden und der Aktionsersteller ist immer derselbe.

Dies ist spezifisch für Redux, und Ihre Eslint-Regel kann dies nicht wissen, daher verstehe ich, warum sie warnen sollte. Sie fragen sich immer noch, ob die Bereitstellung eines leeren Arrays die Regel vielleicht einfach deaktivieren sollte? Ich mag es, dass die Regel mich über fehlende Deps informiert, wenn ich einige, aber nicht alle angegeben habe, oder wenn ich überhaupt keine angegeben habe. Leeres Array bedeutet für mich etwas anderes. Aber das könnte nur ich sein :)

Danke für all Ihre Mühe! Und um unser Leben als Entwickler besser zu machen :)

Mein Anwendungsfall ist viel einfacher und ich kann natürlich alle Abhängigkeiten hinzufügen und es wird immer noch genauso funktionieren, aber ich hatte den Eindruck, dass die Regel "warnen" wird, wenn Sie einige Abhängigkeiten haben, andere jedoch fehlen.

Der Anwendungsfall von

Ich stimme auch zu, dass in diesen Fällen die Inline-Deaktivierung der Regel die beste Wahl ist. In diesem Fall wissen Sie genau, was Sie tun.

Ich glaube, meine ganze Verwirrung war [] vs [some], und natürlich danke @gaearon für die großartige Arbeit :)

Ich denke, ich und @asylejmani sind hier auf derselben Seite, aber ich denke, was Sie @gaearon sagen, ist, dass wir wahrscheinlich falsch liegen, den Effekt nur auf mount auszuführen, wenn wir tatsächlich Abhängigkeiten haben. Ist das eine faire Aussage?

Jawohl. Wenn Ihre Komponente keine Updates für eine Requisite verarbeitet, ist sie normalerweise fehlerhaft. Das Design von useEffect zwingt Sie, sich damit auseinanderzusetzen. Sie können es natürlich umgehen, aber die Standardeinstellung ist, Sie dazu zu bringen, diese Fälle zu behandeln. Dieser Kommentar erklärt es gut: https://github.com/facebook/react/issues/14920#issuecomment -470913287.

Beide Abhängigkeiten kommen von Redux. Die Daten (in diesem Fall) sollten nur einmal geladen werden und der Aktionsersteller ist immer derselbe.

Wenn es gleich ist, wird es Ihnen nicht schaden, es in die Abhängigkeiten aufzunehmen. Ich möchte es betonen – wenn Sie sicher sind, dass sich Ihre Abhängigkeiten nie ändern, kann es nicht schaden, sie aufzulisten . Sollten sie sich jedoch später ändern (z. B. wenn eine übergeordnete Komponente je nach Status eine andere Funktion übergibt), wird Ihre Komponente dies korrekt verarbeiten.

Sie fragen sich immer noch, ob die Bereitstellung eines leeren Arrays die Regel vielleicht einfach deaktivieren sollte?

Nein. Ein leeres Array bereitzustellen und sich dann zu fragen, warum einige Requisiten oder Zustände veraltet sind, ist buchstäblich der häufigste Fehler.

>

Macht sehr viel Sinn, danke

Am 8. März 2019 um 15:27 Uhr schrieb Dan Abramov [email protected] :

Ich denke, ich und @asylejmani sind hier auf derselben Seite, aber ich denke, was Sie @gaearon sagen, ist, dass wir wahrscheinlich falsch liegen, den Effekt nur auf mount auszuführen, wenn wir tatsächlich Abhängigkeiten haben. Ist das eine faire Aussage?

Jawohl. Wenn Ihre Komponente keine Updates für eine Requisite verarbeitet, ist sie normalerweise fehlerhaft. Das Design von useEffect zwingt Sie, sich damit auseinanderzusetzen. Sie können es natürlich umgehen, aber die Standardeinstellung ist, Sie dazu zu bringen, diese Fälle zu behandeln. Dieser Kommentar erklärt es gut: #14920 (Kommentar).

Beide Abhängigkeiten kommen von Redux. Die Daten (in diesem Fall) sollten nur einmal geladen werden und der Aktionsersteller ist immer derselbe.

Wenn es gleich ist, wird es Ihnen nicht schaden, es in die Abhängigkeiten aufzunehmen. Ich möchte es betonen – wenn Sie sicher sind, dass sich Ihre Abhängigkeiten nie ändern, kann es nicht schaden, sie aufzulisten. Sollten sie sich jedoch später ändern (z. B. wenn eine übergeordnete Komponente je nach Status eine andere Funktion übergibt), wird Ihre Komponente dies korrekt verarbeiten.

Sie fragen sich immer noch, ob die Bereitstellung eines leeren Arrays die Regel vielleicht einfach deaktivieren sollte?

Nein. Ein leeres Array bereitzustellen und sich dann zu fragen, warum einige Requisiten oder Zustände veraltet sind, ist buchstäblich der häufigste Fehler.


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail, zeigen Sie sie auf GitHub an oder schalten Sie den Thread stumm.

@aweary

Dies würde dazu führen, dass der Nebeneffekt auftritt, bevor das eigentliche Update festgeschrieben wird, was dazu führen kann, dass der Nebeneffekt häufiger als gewünscht ausgelöst wird.

Ich bin mir nicht sicher, was das bedeutet. Können Sie ein Beispiel geben?

Wir haben diese heute mit @threepointone überflogen . Hier ist eine Zusammenfassung:

In der Fusselregel behoben

Überflüssige useEffect Abhängigkeiten

Die Regel hindert Sie nicht mehr daran, "fremde" Deps zu useEffect hinzuzufügen, da es legitime Szenarien gibt.

Funktionen in derselben Komponente, aber außerhalb des Effekts definiert

Linter warnt nicht für Fälle, in denen es jetzt sicher ist, aber in allen anderen Fällen gibt es Ihnen bessere Vorschläge (z. B. das Verschieben der Funktion innerhalb des Effekts oder das Umschließen mit useCallback ).

Behebung im Benutzercode wert

Zurücksetzen des Status bei Requisitenänderung

Dies erzeugt keine Lint-Verletzungen mehr, aber die idiomatische Art und Weise, den Status als Reaktion auf Requisiten zurückzusetzen, ist anders . Diese Lösung hat ein zusätzliches inkonsistentes Rendering, daher bin ich nicht sicher, ob dies wünschenswert ist.

"Mein Nicht-Funktionswert ist konstant"

Hooks stoßen Sie wo immer möglich zur Korrektheit. Wenn Sie die deps tun angeben (die in einigen Fällen können Sie weglassen), empfehlen wir dringend , auch diejenigen sind , dass Sie nicht ändern denken. Ja, in diesem useDebounce Beispiel wird sich die Verzögerung wahrscheinlich nicht ändern. Aber es ist immer noch ein Fehler, wenn dies der Fall ist, aber der Hook kann damit nicht umgehen. Dies zeigt sich auch in anderen Szenarien. (zB Hooks sind viel besser mit Hot Reloading kompatibel, da jeder Wert als dynamisch behandelt wird.)

Wenn Sie unbedingt darauf bestehen, dass ein bestimmter Wert statisch ist, können Sie ihn erzwingen.
Am sichersten tun Sie dies explizit in Ihrer API:

const useFetch = createFetch({ /* config object */});
const useDebounce = createDebounce(500);
const FormInput = createInput({ rules: [emailValidator, phoneValidator] });

Dann kann es sich eindeutig nicht ändern, es sei denn, Sie legen es in Rendering. (Was keine idiomatische Verwendung Ihres Hooks wäre.) Aber zu sagen, dass sich <Slider min={50} /> niemals ändern kann, ist nicht wirklich gültig – jemand könnte es leicht in <Slider min={state ? 50 : 100} /> ändern. Tatsächlich könnte jemand dies tun:

let slider
if (isCelsius) {
  slider = <Slider min={0} max={100} />
} else {
  slider = <Slider min={32} max={212} />
}

Wenn jemand isCelsius in den Zustand wechselt, wird eine Komponente, die davon ausgeht, dass sich min nie ändert, nicht aktualisiert. Es ist in diesem Fall nicht offensichtlich, dass Slider derselbe ist (aber es liegt daran, dass er dieselbe Position im Baum hat). Dies ist also eine wichtige Waffe, wenn es darum geht, Änderungen am Code vorzunehmen. Ein wichtiger Punkt von React ist, dass Updates genau wie die Anfangszustände gerendert werden (Sie können normalerweise nicht sagen, was welcher ist). Egal, ob Sie den Requisitenwert B rendern oder von Requisitenwert A zu B wechseln – es sollte gleich aussehen und sich verhalten.

Obwohl dies nicht ratsam ist, könnte der Durchsetzungsmechanismus in einigen Fällen ein Hook sein, der warnt, wenn sich der Wert ändert (aber den ersten liefert). Dann fällt es zumindest eher auf.

function useMyHook(a) {
  const initialA = usePossiblyStaleValue(a);
  // ...
}

function usePossiblyStaleValue(value) {
  const ref = useRef(value);

  if (process.env.NODE_ENV !== 'production') {
    if (ref.current !== value) { // Or your custom comparison logic
      console.error(
        'Unlike normally in React, it is not supported ' +
        'to pass dynamic values to useMyHook(). Sorry!'
      );
    }
  }

  return ref.current;
}

Es kann auch einen legitimen Fall geben, in dem Sie mit einem Update einfach nicht umgehen können. Zum Beispiel, wenn die API der unteren Ebene dies nicht unterstützt, wie ein jQuery-Plugin oder eine DOM-API. In diesem Fall ist eine Warnung dennoch angebracht, damit der Verbraucher Ihrer Komponente sie versteht. Alternativ können Sie eine Wrapper-Komponente erstellen, die key bei inkompatiblen Updates zurücksetzt – wodurch ein sauberes Remounten mit neuen Requisiten erzwungen wird. Dies ist wahrscheinlich für Blattkomponenten wie Schieberegler oder Kontrollkästchen vorzuziehen.

"Mein Funktionswert ist konstant"

Zuallererst, wenn es konstant ist und auf höchstes Niveau gehoben wird, wird sich der Linter nicht beschweren. Aber das hilft nicht bei Dingen, die aus Requisiten oder Kontext kommen.

Wenn es wirklich konstant ist dann in deps Angabe nicht verletzt. Zum Beispiel in dem Fall, in dem eine setState Funktion innerhalb eines benutzerdefinierten Hooks an Ihre Komponente zurückgegeben wird und Sie sie dann von einem Effekt aus aufrufen. Die Lint-Regel ist nicht schlau genug, um solche Indirektionen zu verstehen. Aber auf der anderen Seite kann jeder diesen Rückruf später vor der Rückkehr einschließen und möglicherweise auf eine andere Eigenschaft oder einen anderen Zustand darin verweisen. Dann wird es nicht konstant sein! Und wenn Sie diese Änderungen nicht handhaben, werden Sie böse veraltete Prop/State-Bugs haben. Es ist also eine bessere Standardeinstellung, es anzugeben.

Es ist jedoch ein Irrglaube, dass Funktionswerte notwendigerweise konstant sind. Sie sind in Klassen aufgrund der Methodenbindung häufiger konstant, obwohl dies eine eigene Reihe von Fehlern erzeugt . Aber im Allgemeinen kann jede Funktion, die über einem Wert in einer Funktionskomponente schließt, nicht als konstant angesehen werden. Die Fusselregel ist jetzt intelligenter, wenn es darum geht, Ihnen zu sagen, was zu tun ist. (Zum Beispiel das Verschieben in den Effekt – die einfachste Lösung – oder das Umschließen mit useCallback .)

Es gibt ein Problem im entgegengesetzten Spektrum davon, wo Sie Endlosschleifen erhalten (ein Funktionswert ändert sich immer). Wir fangen das jetzt, wenn möglich, in der lint-Regel ab (in derselben Komponente) und schlagen eine Lösung vor. Aber es ist knifflig, wenn Sie etwas mehrere Ebenen weitergeben.

Sie können es immer noch in useCallback umschließen, um das Problem zu beheben. Denken Sie daran, dass es technisch gesehen onChange={shouldHandle ? handleChange : null} oder foo ? <Page fetch={fetchComments /> : <Page fetch={fetchArticles /> an derselben Stelle rendern. Oder sogar fetchComments das über den Zustand der übergeordneten Komponente schließt. Das kann sich ändern. Bei Klassen ändert sich sein Verhalten im Hintergrund, aber die Funktionsreferenz bleibt gleich. Ihr Kind wird dieses Update also verpassen – Sie haben keine andere Möglichkeit, als dem Kind mehr Daten zu übermitteln. Bei Funktionskomponenten und useCallback ändert sich die Funktionsidentität selbst – aber nur bei Bedarf. Das ist also eine nützliche Eigenschaft und nicht nur ein Hindernis, das es zu vermeiden gilt.

Wir sollten eine bessere Lösung zum Erkennen von unendlichen asynchronen Schleifen hinzufügen. Das sollte den verwirrendsten Aspekt mildern. Wir könnten in Zukunft eine Erkennung dafür hinzufügen. Du könntest auch selbst so etwas schreiben:

useWarnAboutTooFrequentChanges([deps]);

Dies ist nicht ideal, und wir müssen darüber nachdenken, wie man dies eleganter handhabt. Ich stimme zu, dass Fälle wie dieser ziemlich unangenehm sind. Die Lösung ohne die Regel zu brechen wäre, rules statisch zu machen, zB indem man die API in createTextInput(rules) ändert und register und unregister in useCallback . Noch besser, entfernen Sie register und unregister und ersetzen Sie sie durch einen separaten Kontext, in dem Sie nur dispatch einfügen. Dann können Sie garantieren, dass Sie nie eine andere Funktionsidentität haben, als sie zu lesen.

Ich würde hinzufügen, dass Sie wahrscheinlich sowieso useMemo auf den Kontextwert setzen möchten, da der Anbieter viele Berechnungen durchführt, deren Wiederholung traurig wäre, wenn keine neuen Komponenten registriert, sondern seine eigenen Eltern aktualisiert würden. So wird das Problem, das Sie sonst vielleicht nicht bemerkt hätten, auf diese Weise sichtbarer. Obwohl ich zustimme, müssen wir es noch bekannter machen, wenn dies geschieht.

Das Ignorieren von Funktionsabhängigkeiten führt zu schlimmeren Fehlern bei Funktionskomponenten und Hooks, da sie andauernd veraltete Requisiten und Zustände sehen würden, wenn Sie dies tun. Versuchen Sie es also nicht, wenn Sie können.

Reagieren auf zusammengesetzte Wertänderungen

Für mich ist es seltsam, warum dieses Beispiel einen Effekt für etwas verwendet, das im Wesentlichen ein Ereignishandler ist. Dasselbe "Log" (ich nehme an, es könnte ein Formular-Senden sein) in einem Event-Handler scheint passender zu sein. Dies gilt insbesondere, wenn wir darüber nachdenken, was passiert, wenn eine Komponente ausgehängt wird. Was ist, wenn die Bereitstellung direkt nach der Planung des Effekts aufgehoben wird? Dinge wie das Absenden von Formularen sollten in diesem Fall nicht einfach "nicht passieren". Es scheint also, dass der Effekt dort eine falsche Wahl sein könnte.

Das heißt, Sie können immer noch tun, was Sie versucht haben – indem Sie fullName stattdessen zu setSubmittedData({firstName, lastName}) , und dann ist [submittedData] Ihre Abhängigkeit, aus der Sie firstName ablesen können. lastName .

Integration mit Imperative/Legacy-Code

Bei der Integration mit zwingenden Dingen wie jQuery-Plugins oder Raw-DOM-APIs kann mit einigen Unannehmlichkeiten gerechnet werden. Trotzdem würde ich erwarten, dass Sie die Effekte in diesem Beispiel etwas mehr konsolidieren können.


Ich hoffe, ich habe niemanden vergessen! Lassen Sie mich wissen, wenn ich es getan habe oder etwas unklar ist. Wir werden versuchen, die Lehren daraus in Kürze in einige Dokumente umzuwandeln.

@gaearon , einzutauchen und Aktionspunkte in verschiedene Kategorien zusammenzufassen. Sie haben mein Beispiel (#14920 (Kommentar) von @trevorgithub) in Ihrem abschließenden Zusammenfassungskommentar verpasst. (Ich schätze es auf jeden Fall, dass es viele Rückmeldungen von vielen Leuten gab; ich denke, mein ursprünglicher Kommentar ist irgendwo in der Mitte der Problemkommentare im Abschnitt versteckte Elemente verloren gegangen).

Ich gehe davon aus, dass mein Beispiel unter "Integration mit Imperative/Legacy-Code" fallen würde, obwohl möglicherweise auch andere Kategorie(n)?

Bei Problemen mit der 'Integration mit Imperative/Legacy-Code' klingt es so, als ob nicht viel getan werden kann. Wie würde man in diesen Fällen diese Warnung ignorieren? Ich vermute:
// eslint-disable-line react-hooks/exhaustive-deps

Entschuldigung, dass ich diesen verpasst habe.

Wenn Sie einige Daten über Requisiten erhalten, diese Requisiten jedoch erst nach einer expliziten Änderung verwenden möchten, klingt es nach einer korrekten Methode, um einen abgeleiteten Zustand zu modellieren.

Du denkst darüber nach: "Ich möchte eine Änderung an einer Requisite bis zu einer anderen Requisite ignorieren". Sie können es sich aber auch so vorstellen: „Meine Komponente hat eine Abruffunktion im Zustand. Es wird von einer Requisite aktualisiert, wenn sich eine andere Requisite ändert.“

Ein allgemein abgeleiteter Zustand wird nicht empfohlen, aber hier scheint es das zu sein, was Sie wollen. Der einfachste Weg, dies zu implementieren, wäre etwa:

const [currentGetData, setCurrentGetData] = useState(getData);
const [prevRefreshRequest, setPrevRefreshRequest] = useState(refreshRequest);

if (prevRefreshRequest !== refreshRequest) {
  setPrevRefreshRequest(refreshRequest);
  setCurrentGetData(getData);
}

useEffect(() => {
  currentGetData(someId);
}, [currentGetData, someId]);

Sie müssten auch useCallback um getData herumlegen, die Sie weitergeben.

Beachten Sie, dass mir das Muster der Weitergabe asynchroner Funktionen zum Abrufen im Allgemeinen zwielichtig erscheint. Ich denke, in Ihrem Fall verwenden Sie Redux, damit das Sinn macht. Wenn jedoch eine asynchrone Funktion in einer übergeordneten Komponente definiert wurde, wäre dies verdächtig, da Sie wahrscheinlich Racebedingungen haben würden. Ihre Effekte haben keine Bereinigung. Wie können Sie also wissen, wann der Benutzer eine andere ID wählt? Es besteht die Gefahr, dass Anfragen nicht in der richtigen Reihenfolge eingehen und den falschen Status festlegen. Das ist also nur etwas, das man im Hinterkopf behalten sollte. Wenn das Abrufen von Daten in der Komponente selbst erfolgt, können Sie eine Effektbereinigungsfunktion verwenden, die ein "ignore"-Flag setzt, um einen setState von der Antwort zu verhindern. (Natürlich ist das Verschieben von Daten in einen externen Cache oft die bessere Lösung – so funktioniert auch Suspense.)

Dasselbe "Log" (ich nehme an, es könnte ein Formular-Senden sein) in einem Event-Handler scheint passender zu sein.

@gaearon Das Problem, das ich sehe, ist, dass dies im Ereignishandler bedeutet, dass der Nebeneffekt auftritt, bevor das Update festgeschrieben wird. Es gibt keine strikte Garantie dafür, dass die Komponente als Ergebnis dieses Ereignisses erfolgreich erneut gerendert wird, daher kann es verfrüht sein, dies im Ereignishandler zu tun.

Zum Beispiel, wenn ich protokollieren möchte, dass der Benutzer erfolgreich eine neue Suchanfrage gesendet hat und die Ergebnisse anzeigt. Wenn etwas schief geht und die Komponente ausgelöst wird, möchte ich nicht, dass dieses Protokollereignis aufgetreten ist.

Dann gibt es den Fall, in dem dieser Nebeneffekt asynchron sein könnte, sodass Sie mit useEffect die Bereinigungsfunktion erhalten.

Es gibt auch das Problem von useReducer , bei dem der Wert, den ich möglicherweise protokollieren möchte, im Ereignishandler nicht verfügbar ist. Aber ich glaube das ist schon auf eurem Radar

In beiden Fällen ist der von Ihnen empfohlene Ansatz wahrscheinlich ausreichend. Speichern Sie den zusammengesetzten Zustand in einem Formular, in dem Sie noch auf die einzelnen Werte zugreifen können, die er zusammengestellt hat.

Ich habe einen praktischen Hook zum Umhüllen von Funktionen mit zusätzlichen Parametern und zum Weitergeben dieser. Es sieht aus wie das:

function useBoundCallback(fn, ...bound) {
  return useCallback((...args) => fn(...bound, ...args), [fn, ...bound]);
}

(eigentlich ist es etwas komplizierter, weil es einige zusätzliche Funktionen hat, aber das obige zeigt immer noch das relevante Problem).

Der Anwendungsfall liegt vor, wenn eine Komponente eine Eigenschaft an ein untergeordnetes Element weitergeben muss und möchte, dass das untergeordnete Element die Möglichkeit hat, diese bestimmte Eigenschaft zu ändern. Betrachten Sie beispielsweise eine Liste, in der jedes Element eine Bearbeitungsoption hat. Das Elternobjekt übergibt jedem Kind einen Wert und eine Callback-Funktion zum Aufrufen, wenn es den Wert bearbeiten möchte. Das untergeordnete Element weiß nicht, welche Element-ID angezeigt wird, daher muss das übergeordnete Element den Rückruf ändern, um diesen Parameter einzuschließen. Dies kann beliebig tief verschachtelt werden.

Ein vereinfachtes Beispiel für diesen Ablauf wird hier gezeigt: https://codesandbox.io/s/vvv36834k5 (ein Klick auf "Los!" zeigt ein Konsolenprotokoll an, das den Pfad der Komponenten enthält)


Das Problem ist, dass ich mit dieser Regel 2 Linter-Fehler bekomme:

React Hook (X) hat eine fehlende Abhängigkeit: 'bound'. Entweder einschließen oder das Abhängigkeitsarray entfernen

React Hook (X) hat ein Spread-Element in seinem Abhängigkeitsarray. Das bedeutet, dass wir nicht statisch überprüfen können, ob Sie die richtigen Abhängigkeiten übergeben haben

Eine Änderung zur Verwendung einer Parameterliste (dh keine Verwendung des Spread-Operators) würde die Memoisierung zerstören, da die Liste bei jedem Aufruf erstellt wird, selbst wenn die Parameter identisch sind.


Die Gedanken:

  • Lohnt es sich, den ersten Fehler anzuzeigen, wenn der zweite auch zutrifft?
  • Gibt es eine Möglichkeit, diese Regel speziell für Orte zu deaktivieren, an denen der Spread-Operator verwendet wird?
  • Wenn die einzige Verwendung einer Variablen innerhalb einer Funktion der Spread-Operator ist, ist es sicher, den Spread-Operator in den Abhängigkeiten zu verwenden, und da die Regel dies bereits erkennt, sollte sie dies sicherlich zulassen. Mir ist klar, dass es komplexere Fälle gibt, die mit statischer Analyse schwieriger zu lösen sind, aber dies scheint ein einfacher Gewinn für eine relativ häufige Verwendung von Spread zu sein.

Hallo @gaearon , ich habe gerade diese Warnung bekommen, die ich nirgendwo diskutiert gefunden habe:

Accessing 'myRef.current' during the effect cleanup will likely read a different ref value because by this time React has already updated the ref. If this ref is managed by React, store 'myRef.current' in a variable inside the effect itself and refer to that variable from the cleanup function.

Ich finde diese Meldung etwas verwirrend. Ich denke, es versucht mich zu warnen, dass der Referenzstromwert bei der Bereinigung vom Wert am Effektkörper abweichen kann. Rechts?
Wenn dies der Fall ist und sich dessen bewusst ist, ist es dann sicher/legitim, diese Warnung zu ignorieren?

Mein Fall, falls interessant: CodeSandbox
Kontext: ein benutzerdefinierter Hook zum Abrufen von Daten. Ich verwende einen Zähler in einem Ref, um sowohl Race-Conditions als auch Updates für nicht gemountete Komponenten zu verhindern.

Ich denke, ich könnte diese Warnung umgehen, indem ich die gelesene Referenz in einer Funktion verstecke oder eine andere boolesche Referenz für den Bereinigungsfall erstelle. Aber ich finde es unnötig langatmig, wenn ich diese Warnung einfach ignorieren kann.

@aweary

Zum Beispiel, wenn ich protokollieren möchte, dass der Benutzer erfolgreich eine neue Suchanfrage gesendet hat und die Ergebnisse anzeigt. Wenn etwas schief geht und die Komponente ausgelöst wird, möchte ich nicht, dass dieses Protokollereignis aufgetreten ist.

Ja, das klingt nach einem Nischen-Use-Case. Ich denke, wenn die meisten Leute "Nebenwirkungen" wollen, meinen sie damit die Formulareinreichung selbst – nicht die Tatsache, dass Sie ein eingereichtes Formular angesehen haben. In diesem Fall scheint die von mir bereitgestellte Lösung in Ordnung zu sein.

@davidje13

Lohnt es sich, den ersten Fehler anzuzeigen, wenn der zweite auch zutrifft?

Bitte reichen Sie eine neue Ausgabe ein, um Vorschläge zur Änderung der Lint-Regel zu erhalten.

Gibt es eine Möglichkeit, diese Regel speziell für Orte zu deaktivieren, an denen der Spread-Operator verwendet wird?

Du kannst immer // eslint-disable-next-line react-hooks/exhaustive-deps wenn du denkst, dass du weißt, was du tust.

Wenn die einzige Verwendung einer Variablen innerhalb einer Funktion der Spread-Operator ist, ist es sicher, den Spread-Operator in den Abhängigkeiten zu verwenden, und da die Regel dies bereits erkennt, sollte sie dies sicherlich zulassen.

Bitte ein neues Problem melden.

@CarlosGines

Ich finde diese Meldung etwas verwirrend. Ich denke, es versucht mich zu warnen, dass der Referenzstromwert bei der Bereinigung vom Wert am Effektkörper abweichen kann. Rechts?

Jawohl.

Wenn dies der Fall ist und sich dessen bewusst ist, ist es dann sicher/legitim, diese Warnung zu ignorieren?

Ähm.. nicht, wenn das zu einem Fehler führt. 🙂

Kontext: ein benutzerdefinierter Hook zum Abrufen von Daten. Ich verwende einen Zähler in einem Ref, um sowohl Race-Conditions als auch Updates für nicht gemountete Komponenten zu verhindern.

Ja, vielleicht ist dieser Anwendungsfall legitim. Eine neue Ausgabe einreichen, um pls zu besprechen?

Ich werde dieses Problem schließen, da wir genug Feedback erhalten haben und es integriert wurde.

Häufige Fragen & Antworten: https://github.com/facebook/react/issues/14920#issuecomment -471070149

Wenn Sie einen tiefen Einblick in useEffect und Abhängigkeiten wünschen, finden Sie es hier: https://overreacted.io/a-complete-guide-to-useeffect/

Wir werden in Kürze auch weitere Elemente zu den Dokumenten hinzufügen.

Wenn Sie etwas an der Regel ändern möchten oder sich nicht sicher sind, ob Ihr Fall legitim ist, reichen Sie ein neues Problem ein.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen