Ich habe mit dem neuen Hook-System in React 16.7-alpha herumgespielt und stecke in useEffect in einer Endlosschleife fest, wenn der Status, den ich verarbeite, ein Objekt oder Array ist.
Zuerst benutze ich useState und initiiere es mit einem leeren Objekt wie folgt:
const [obj, setObj] = useState({});
Dann benutze ich in useEffect setObj, um es wieder auf ein leeres Objekt zu setzen. Als zweites Argument übergebe ich [obj] in der Hoffnung, dass es nicht aktualisiert wird, wenn sich der Inhalt des Objekts nicht geändert hat. Aber es wird ständig aktualisiert. Ich denke, weil dies unabhängig vom Inhalt immer unterschiedliche Objekte sind, die React denken lassen, dass es sich ständig ändert?
useEffect(() => {
setIngredients({});
}, [ingredients]);
Das gleiche gilt für Arrays, aber als Grundelement bleibt es nicht wie erwartet in einer Schleife hängen.
Wie soll ich mit diesen neuen Hooks mit Objekten und Arrays umgehen, wenn überprüft wird, ob sich der Inhalt geändert hat oder nicht?
quelle
Antworten:
Wenn Sie ein leeres Array als zweites Argument für useEffect übergeben, wird es nur beim Ein- und Aushängen ausgeführt, wodurch Endlosschleifen gestoppt werden.
useEffect(() => { setIngredients({}); }, []);
Dies wurde mir im Blogbeitrag zu React Hooks unter https://www.robinwieruch.de/react-hooks/ klargestellt.
quelle
Hatte das gleiche Problem. Ich weiß nicht, warum sie dies in Dokumenten nicht erwähnen. Ich möchte nur ein wenig zur Antwort von Tobias Haugen hinzufügen.
Um in jeder Komponente / jedem übergeordneten Renderer ausgeführt zu werden , müssen Sie Folgendes verwenden:
useEffect(() => { // don't know where it can be used :/ })
Um etwas nur einmal nach dem Mounten der Komponente auszuführen (wird einmal gerendert), müssen Sie Folgendes verwenden:
useEffect(() => { // do anything only one time if you pass empty array [] // keep in mind, that component will be rendered one time (with default values) before we get here }, [] )
So führen Sie etwas einmalig auf der Komponentenmontage und bei Daten- / Daten2- Änderungen aus:
const [data, setData] = useState(false) const [data2, setData2] = useState('default value for first render') useEffect(() => { // if you pass some variable, than component will rerender after component mount one time and second time if this(in my case data or data2) is changed // if your data is object and you want to trigger this when property of object changed, clone object like this let clone = JSON.parse(JSON.stringify(data)), change it clone.prop = 2 and setData(clone). // if you do like this 'data.prop=2' without cloning useEffect will not be triggered, because link to data object in momory doesn't changed, even if object changed (as i understand this) }, [data, data2] )
Wie ich es die meiste Zeit benutze:
export default function Book({id}) { const [book, bookSet] = useState(false) useEffect(() => { loadBookFromServer() }, [id]) // every time id changed, new book will be loaded // Remeber that it's not always safe to omit functions from the list of dependencies. Explained in comments. async function loadBookFromServer() { let response = await fetch('api/book/' + id) response = await response.json() bookSet(response) } if (!book) return false //first render, when useEffect did't triggered yet we will return false return <div>{JSON.stringify(book)}</div> }
quelle
Ich bin auch einmal auf dasselbe Problem gestoßen und habe es behoben, indem ich sichergestellt habe, dass ich im zweiten Argument primitive Werte übergebe
[]
.Wenn Sie ein Objekt übergeben, speichert React nur die Referenz auf das Objekt und führt den Effekt aus, wenn sich die Referenz ändert, was normalerweise jedes Mal der Fall ist (ich weiß jetzt aber nicht wie).
Die Lösung besteht darin, die Werte im Objekt zu übergeben. Du kannst es versuchen,
const obj = { keyA: 'a', keyB: 'b' } useEffect(() => { // do something }, [Object.values(obj)]);
oder
const obj = { keyA: 'a', keyB: 'b' } useEffect(() => { // do something }, [obj.keyA, obj.keyB]);
quelle
0
an das Abhängigkeitsarray übergeben?Wie in der Dokumentation ( https://reactjs.org/docs/hooks-effect.html ) angegeben, soll der
useEffect
Hook verwendet werden, wenn nach jedem Rendern Code ausgeführt werden soll . Aus den Dokumenten:Wenn Sie dies anpassen möchten, können Sie den Anweisungen folgen, die später auf derselben Seite angezeigt werden ( https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects ). Grundsätzlich
useEffect
akzeptiert die Methode ein zweites Argument , das React prüft, um festzustellen, ob der Effekt erneut ausgelöst werden muss oder nicht.useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // Only re-run the effect if count changes
Sie können jedes Objekt als zweites Argument übergeben. Wenn dieses Objekt unverändert bleibt, wird Ihr Effekt erst nach dem ersten Mount ausgelöst . Wenn sich das Objekt ändert, wird der Effekt erneut ausgelöst.
quelle
Wenn Sie einen benutzerdefinierten Hook erstellen , können Sie manchmal eine Endlosschleife mit der folgenden Standardeinstellung verursachen
function useMyBadHook(values = {}) { useEffect(()=> { /* This runs every render, if values is undefined */ }, [values] ) }
Die Lösung besteht darin, dasselbe Objekt zu verwenden, anstatt bei jedem Funktionsaufruf ein neues zu erstellen:
const defaultValues = {}; function useMyBadHook(values = defaultValues) { useEffect(()=> { /* This runs on first call and when values change */ }, [values] ) }
Wenn Sie in Ihrem Komponentencode darauf stoßen, wird die Schleife möglicherweise behoben, wenn Sie defaultProps anstelle von ES6-Standardwerten verwenden
function MyComponent({values}) { useEffect(()=> { /* do stuff*/ },[values] ) return null; /* stuff */ } MyComponent.defaultProps = { values = {} }
quelle
Ich bin nicht sicher, ob dies für Sie funktioniert, aber Sie könnten versuchen, .length wie folgt hinzuzufügen:
useEffect(() => { // fetch from server and set as obj }, [obj.length]);
In meinem Fall (ich habe ein Array abgerufen!) Wurden Daten beim Mounten abgerufen, dann wieder nur beim Ändern, und es ging nicht in eine Schleife.
quelle
Wenn Sie am Ende von useEffect ein leeres Array einfügen :
useEffect(()=>{ setText(text); },[])
Es würde einmal laufen.
Wenn Sie auch Parameter in das Array aufnehmen:
useEffect(()=>{ setText(text); },[text])
Es wird immer dann ausgeführt, wenn sich die Textparameter ändern.
quelle
Ihre Endlosschleife ist auf Zirkularität zurückzuführen
useEffect(() => { setIngredients({}); }, [ingredients]);
setIngredients({});
ändert den Wert voningredients
(gibt jedes Mal eine neue Referenz zurück), der ausgeführt wirdsetIngredients({})
. Um dies zu lösen, können Sie einen der folgenden Ansätze verwenden:const timeToChangeIngrediants = ..... useEffect(() => { setIngredients({}); }, [timeToChangeIngrediants ]);
setIngrediants
wird ausgeführt, wenntimeToChangeIngrediants
sich geändert hat.Object.values(ingrediants)
als zweites Argument useEffect.useEffect(() => { setIngredients({}); }, Object.values(ingrediants));
quelle
Ich glaube, sie versuchen, die Möglichkeit auszudrücken, dass man veraltete Daten verwenden könnte, und sich dessen bewusst zu sein. Es spielt keine Rolle, welche Art von Werten wir
array
für das zweite Argument senden, solange wir wissen, dass der Effekt ausgeführt wird, wenn sich einer dieser Werte ändert. Wenn wiringredients
als Teil der Berechnung innerhalb des Effekts verwenden, sollten wir ihn in diearray
.const [ingredients, setIngredients] = useState({}); // This will be an infinite loop, because by shallow comparison ingredients !== {} useEffect(() => { setIngredients({}); }, [ingredients]); // If we need to update ingredients then we need to manually confirm // that it is actually different by deep comparison. useEffect(() => { if (is(<similar_object>, ingredients) { return; } setIngredients(<similar_object>); }, [ingredients]);
quelle
Der beste Weg ist, den vorherigen Wert mit dem aktuellen Wert zu vergleichen, indem Sie usePrevious () und _.isEqual () von Lodash verwenden . Import isEqual und useRef . Vergleichen Sie Ihren vorherigen Wert mit dem aktuellen Wert in useEffect () . Wenn sie gleich sind, aktualisieren Sie nichts anderes. usePrevious (Wert) ist ein benutzerdefinierter Haken , die einen schafft ref mit useRef () .
Unten ist ein Ausschnitt meines Codes. Ich hatte das Problem einer Endlosschleife beim Aktualisieren von Daten mithilfe des Firebase-Hooks
import React, { useState, useEffect, useRef } from 'react' import 'firebase/database' import { Redirect } from 'react-router-dom' import { isEqual } from 'lodash' import { useUserStatistics } from '../../hooks/firebase-hooks' export function TMDPage({ match, history, location }) { const usePrevious = value => { const ref = useRef() useEffect(() => { ref.current = value }) return ref.current } const userId = match.params ? match.params.id : '' const teamId = location.state ? location.state.teamId : '' const [userStatistics] = useUserStatistics(userId, teamId) const previousUserStatistics = usePrevious(userStatistics) useEffect(() => { if ( !isEqual(userStatistics, previousUserStatistics) ) { doSomething() } })
quelle
Falls Sie das Objekt vergleichen müssen und wenn es aktualisiert wird, finden Sie hier einen
deepCompare
Haken zum Vergleich. Die akzeptierte Antwort spricht das sicherlich nicht an. Ein[]
Array ist geeignet, wenn der Effekt beim Mounten nur einmal ausgeführt werden soll.Andere abgestimmte Antworten befassen sich nur mit einer Prüfung auf primitive Typen, indem sie
obj.value
etwas Ähnliches tun , um zuerst die Ebene zu erreichen, auf der sie nicht verschachtelt sind. Dies ist möglicherweise nicht der beste Fall für tief verschachtelte Objekte.Hier ist also eine, die in allen Fällen funktioniert.
import { DependencyList } from "react"; const useDeepCompare = ( value: DependencyList | undefined ): DependencyList | undefined => { const ref = useRef<DependencyList | undefined>(); if (!isEqual(ref.current, value)) { ref.current = value; } return ref.current; };
Sie können das gleiche im
useEffect
Haken verwendenReact.useEffect(() => { setState(state); }, useDeepCompare([state]));
quelle
Sie können das Objekt auch im Abhängigkeitsarray zerstören, dh der Status wird nur aktualisiert, wenn bestimmte Teile des Objekts aktualisiert werden.
Nehmen wir für dieses Beispiel an, die Zutaten enthielten Karotten. Wir könnten dies an die Abhängigkeit übergeben, und nur wenn sich die Karotten ändern, wird der Status aktualisiert.
Sie könnten dies dann weiter verfolgen und die Anzahl der Karotten nur an bestimmten Punkten aktualisieren, um so zu steuern, wann der Status aktualisiert wird, und eine Endlosschleife zu vermeiden.
useEffect(() => { setIngredients({}); }, [ingredients.carrots]);
Ein Beispiel dafür, wann so etwas verwendet werden könnte, ist die Anmeldung eines Benutzers bei einer Website. Wenn sie sich anmelden, können wir das Benutzerobjekt zerstören, um ihre Cookie- und Berechtigungsrolle zu extrahieren und den Status der App entsprechend zu aktualisieren.
quelle
Mein Fall war etwas Besonderes bei der Begegnung mit einer Endlosschleife. Das Senario war wie folgt:
Ich hatte ein Objekt, sagen wir objX, das von Requisiten stammt, und ich habe es in Requisiten wie:
const { something: { somePropery } } = ObjX
und ich habe das
somePropery
als abhängigkeit zu meinemuseEffect
like benutzt:useEffect(() => { // ... }, [somePropery])
und es verursachte mir eine Endlosschleife, ich versuchte dies zu handhaben, indem ich das Ganze
something
als Abhängigkeit übergab und es funktionierte richtig.quelle