Kann ich den Status innerhalb eines useEffect-Hooks festlegen?

112

Nehmen wir an, ich habe einen Zustand, der von einem anderen Zustand abhängt (z. B. wenn sich A ändert, möchte ich, dass sich B ändert).

Ist es angemessen, einen Hook zu erstellen, der A beobachtet und B innerhalb des useEffect-Hooks setzt?

Werden die Effekte so kaskadiert, dass beim Klicken auf die Schaltfläche der erste Effekt ausgelöst wird, wodurch sich b ändert und der zweite Effekt vor dem nächsten Rendern ausgelöst wird? Gibt es Leistungseinbußen bei der Strukturierung von Code wie diesem?

let MyComponent = props => {
  let [a, setA] = useState(1)
  let [b, setB] = useState(2)
  useEffect(
    () => {
      if (/*some stuff is true*/) {
        setB(3)
      }
    },
    [a],
  )
  useEffect(
    () => {
      // do some stuff
    },
    [b],
  )

  return (
    <button
      onClick={() => {
        setA(5)
      }}
    >
      click me
    </button>
  )
}
Dan Ruswick
quelle

Antworten:

32

Effekte werden immer ausgeführt, nachdem die Renderphase abgeschlossen ist, auch wenn Sie den Status innerhalb eines Effekts festlegen. Ein anderer Effekt liest den aktualisierten Status und reagiert erst nach der Renderphase darauf.

Allerdings ist es wahrscheinlich besser, beide Aktionen mit demselben Effekt auszuführen, es sei denn, es besteht die Möglichkeit, dass bsich dies aus anderen Gründen als changing ain diesem Fall ändern kann. Sie möchten dieselbe Logik ausführen

Shubham Khatri
quelle
4
Wenn also A B ändert, wird die Komponente zweimal richtig gerendert, oder?
Dan Ruswick
2
Ich möchte auch die Antwort auf die obige Frage wissen
Alaboudi
2
@alaboudi Ja, wenn sich A ändert und der Useeffect ausgeführt wird, wodurch B gesetzt wird, wird die Komponente zweimal
gerendert
96

Im Allgemeinen wird durch die Verwendung von setStateinside useEffecteine Endlosschleife erstellt, die Sie höchstwahrscheinlich nicht verursachen möchten. Es gibt einige Ausnahmen von dieser Regel, auf die ich später noch eingehen werde.

useEffectwird nach jedem Rendern aufgerufen und wenn setStatees darin verwendet wird, wird die Komponente erneut gerendert, was aufruft useEffectund so weiter und so fort.

Einer der beliebtesten Fälle, in denen die Verwendung von useStateinside of useEffectkeine Endlosschleife verursacht, ist die Übergabe eines leeren Arrays als zweites Argument an useEffectlike. useEffect(() => {....}, [])Dies bedeutet, dass die Effektfunktion einmal aufgerufen werden sollte: nur nach dem ersten Mount / Render. Dies wird häufig verwendet, wenn Sie Daten in einer Komponente abrufen und die Anforderungsdaten im Status der Komponente speichern möchten.

Hossam Mourad
quelle
3
Ein weiterer Fall Gebrauch setStateinnen useEffectist setting stateinnerhalb Abonnements oder Ereignis - Listener. Aber vergessen Sie nicht, das Abonnement zu kündigen. Reactjs.org/docs/hooks-effect.html#effects-with-cleanup
timqian
29
Dies ist nicht unbedingt der Fall. UseState wird nur ausgelöst, wenn der Wert, mit dem Sie den Status aktualisieren, vom vorherigen abweicht, sodass eine Endlosschleife verhindert wird, sofern sich der Wert nicht zwischen den Zyklen ändert.
RobM
13
Diese Antwort ist falsch und nicht auf den Punkt der Frage: Im Code für den Fall, dass die Komponente nur zweimal rendert, wenn auf die Schaltfläche geklickt wird, gibt es keine Endlosschleife
Bogdan D
14
Es ist ein sehr häufiger Anwendungsfall, den Status in useEffect festzulegen. Denken Sie an das Laden von Daten, useEffect ruft die API auf, ruft Daten ab und legt mithilfe des festgelegten Teils von useState fest.
Badbod99
3
Ich habe die Antwort aktualisiert, um die von Ihnen genannten Probleme zu beheben. Ist es jetzt besser?
Hossam Mourad
49

Für zukünftige Zwecke kann dies auch helfen:

Es ist in Ordnung, setState zu verwenden. useEffectSie müssen nur wie bereits beschrieben aufpassen , um keine Schleife zu erstellen.

Dies ist jedoch nicht das einzige Problem, das auftreten kann. Siehe unten:

Stellen Sie sich vor, Sie haben eine Komponente Comp, die propsvom übergeordneten Element empfangen wird, und gemäß einer propsÄnderung möchten Sie den CompStatus festlegen . Aus irgendeinem Grund müssen Sie für jede Requisite eine andere ändern useEffect:

MACH DAS NICHT

useEffect(() => {
  setState({ ...state, a: props.a });
}, [props.a]);

useEffect(() => {
  setState({ ...state, b: props.b });
}, [props.b]);

Der Status von a wird möglicherweise nie geändert, wie Sie in diesem Beispiel sehen können: https://codesandbox.io/s/confident-lederberg-dtx7w

Der Grund , warum dies in diesem Beispiel passieren , es ist , weil beide useEffects im gleichen Laufzyklus reagieren , wenn Sie beide ändern prop.aund prop.bso der Wert , {...state}wenn Sie tun , setStatesind genau die gleichen in beiden , useEffectweil sie im gleichen Kontext sind. Wenn Sie die zweite ausführen setState, wird die erste ersetzt setState.

TUN SIE DAS STATT

Die Lösung für dieses Problem lautet im Grunde setStatewie folgt :

useEffect(() => {
  setState(state => ({ ...state, a: props.a }));
}, [props.a]);

useEffect(() => {
  setState(state => ({ ...state, b: props.b }));
}, [props.b]);

Überprüfen Sie die Lösung hier: https://codesandbox.io/s/mutable-surf-nynlx

Jetzt erhalten Sie immer den aktuellsten und korrektesten Wert des Status, wenn Sie mit dem fortfahren setState.

Ich hoffe das hilft jemandem!

während wahr
quelle
2
Die obige Lösung hat mir geholfensetName(name => ({ ...name, a: props.a }));
mufaddal_mw
1
Dies half mir auch in dem Teil mit der setItems(items => [...items, item])
Pfeilfunktion
Wow, du bist großartig. Du hast heute meine A * s gerettet.
Pratik Soni
Verbrachte 7 bis 15 Uhr ohne eine Lösung zu haben und jetzt hast du mich gerettet.
Dinindu Kanchana
20

useEffectkann an einer bestimmten Requisite oder einem bestimmten Zustand hängen. Das, was Sie tun müssen, um einen Endlosschleifen-Hook zu vermeiden, ist das Binden einer Variablen oder eines Zustands, um dies zu bewirken

Zum Beispiel:

useEffect(myeffectCallback, [])

Der obige Effekt wird erst ausgelöst, wenn die Komponente gerendert wurde. Dies ähnelt dem componentDidMountLebenszyklus

const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
  setSomething(0)
}, myState)

Der obige Effekt wird nur ausgelöst, componentDidUpdatewenn sich mein Status geändert hat. Dies ist ähnlich, außer dass nicht jeder sich ändernde Status ihn auslöst.

Sie können mehr Details über diesen Link lesen

Dogies007
quelle
Vielen Dank, diese Antwort adressiert das Abhängigkeitsarray von useEffect auf eine Weise, die andere Antworten nicht hatten. Wenn Sie ein leeres Array als zweites Argument für useEffect einfügen, wird sichergestellt, dass useEffect ausgeführt wird, sobald die Komponente gerendert wurde. Wenn Sie jedoch ein Array mit einem bestimmten Status oder bestimmten Status einschließen, wird useEffect ausgeführt, wenn sich die Referenzzustände geändert haben.
Adriaanbd
10

▶ 1. Kann ich den Status innerhalb eines useEffect-Hooks festlegen?

Grundsätzlich können Sie den Status frei festlegen, wo Sie ihn benötigen - auch innerhalb useEffectund sogar während des Renderns . Vermeiden Sie Endlosschleifen, indem Sie den Haken depsrichtig einstellen und / oder bedingt angeben .


▶ 2. Nehmen wir an, ich habe einen Zustand, der von einem anderen Zustand abhängt. Ist es angemessen, einen Hook zu erstellen, der A beobachtet und B innerhalb des useEffect-Hooks setzt?

Sie haben gerade den klassischen Anwendungsfall beschrieben für useReducer:

useReducerist normalerweise vorzuziehen, useStatewenn Sie eine komplexe Zustandslogik haben , die mehrere Unterwerte umfasst , oder wenn der nächste Zustand vom vorherigen abhängt. ( Dokumente reagieren )

Wenn eine Einstellung Zustand Variable abhängt , dem aktuellen Wert des anderen Staates Variable, können Sie sie beide mit dem Ersetzen versuchen wollen useReducer. [...] Wenn Sie schreiben setSomething(something => ...) , ist es ein guter Zeitpunkt, stattdessen einen Reduzierer zu verwenden. ( Dan Abramov, überreagierter Blog )


▶ 3. Werden die Effekte so kaskadiert, dass beim Klicken auf die Schaltfläche der erste Effekt ausgelöst wird, wodurch sich b ändert und der zweite Effekt vor dem nächsten Rendern ausgelöst wird?

useEffectWird immer ausgeführt, nachdem das Rendern festgeschrieben und DOM-Änderungen angewendet wurden. Der erste Effekt wird ausgelöst, geändert bund bewirkt ein erneutes Rendern. Nach Abschluss dieses Renderns wird der zweite Effekt aufgrund von bÄnderungen ausgeführt.


▶ 4. Gibt es Leistungseinbußen bei der Strukturierung von Code wie diesem?

Ja. Durch das Umschließen der Statusänderung bin ein separates useEffectfür averfügt der Browser über eine zusätzliche Layout- / Malphase - diese Effekte sind möglicherweise für den Benutzer sichtbar. Wenn Sie es auf keinen Fall useReducerversuchen möchten , können Sie den bStatus zusammen mit adirekt ändern :

ford04
quelle