Verwendung des Rückrufs "setState" für Reaktions-Hooks

119

React Hooks wird useStatezum Einstellen des Komponentenzustands eingeführt. Aber wie kann ich Hooks verwenden, um den Rückruf wie folgt zu ersetzen:

setState(
  { name: "Michael" },
  () => console.log(this.state)
);

Ich möchte etwas tun, nachdem der Status aktualisiert wurde.

Ich weiß, dass ich useEffectdie zusätzlichen Dinge verwenden kann, aber ich muss den vorherigen Statuswert überprüfen, für den ein Bitcode erforderlich ist. Ich suche eine einfache Lösung, die mit useStateHaken verwendet werden kann.

Joey Yi Zhao
quelle
1
In der Klassenkomponente habe ich Async verwendet und warte darauf, dasselbe Ergebnis zu erzielen wie beim Hinzufügen eines Rückrufs in setState. Leider funktioniert es nicht im Haken. Selbst wenn ich asynchron hinzugefügt und gewartet habe, wartet die Reaktion nicht auf die Aktualisierung des Status. Vielleicht ist useEffect der einzige Weg, dies zu tun.
MING WU
2
@Zhao, du hast noch nicht die richtige Antwort markiert. Können Sie bitte ein paar Sekunden
sparen

Antworten:

128

Sie müssen useEffectHaken verwenden, um dies zu erreichen.

const [counter, setCounter] = useState(0);

const doSomething = () => {
  setCounter(123);
}

useEffect(() => {
   console.log('Do something after counter has changed', counter);
}, [counter]);
Zohaib Ijaz
quelle
48
Dies löst das console.logbeim ersten Rendern sowie alle counterZeitänderungen aus. Was ist, wenn Sie erst etwas tun möchten, nachdem der Status aktualisiert wurde, aber nicht beim ersten Rendern, da der Anfangswert festgelegt ist? Ich denke, Sie könnten den Wert einchecken useEffectund entscheiden, ob Sie dann etwas tun möchten. Wäre das eine bewährte Methode?
Darryl Young
2
Um zu vermeiden, dass useEffectbeim ersten Rendern ausgeführt wird, können Sie einen benutzerdefinierten useEffectHook erstellen , der beim ersten Rendern nicht ausgeführt wird. Um einen solchen Hook zu erstellen, können Sie diese Frage überprüfen: stackoverflow.com/questions/53253940/…
11
Und was ist mit dem Fall, wenn ich verschiedene Rückrufe in verschiedenen setState-Aufrufen aufrufen möchte, wodurch sich der gleiche Statuswert ändert? Ihre Antwort ist falsch und sollte nicht als richtig markiert werden, um Neulinge nicht zu verwirren. Die Wahrheit ist, dass setState-Rückrufe eines der schwierigsten Probleme bei der Migration auf Hooks aus Klassen sind, für die es keine eindeutige Lösungsmethode gibt. Manchmal werden Sie wirklich einen wertabhängigen Effekt haben, und manchmal erfordert es einige hackige Methoden, wie das Speichern einer Art von Flags in Refs.
Dmitry
Anscheinend könnte ich heute setCounter (Wert, Rückruf) aufrufen, damit eine Aufgabe ausgeführt wird, nachdem der Status aktualisiert wurde, wodurch ein useEffect vermieden wird. Ich bin mir nicht ganz sicher, ob dies eine Präferenz oder ein besonderer Vorteil ist.
Ken Ingram
1
@ KenIngram Ich habe es gerade versucht und habe diesen sehr spezifischen Fehler bekommen:Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
Greg Sadetsky
22

Wenn Sie den vorherigen Status aktualisieren möchten, können Sie dies in Hooks tun:

const [count, setCount] = useState(0);


setCount(previousCount => previousCount + 1);
Bimal Grg
quelle
2
Ich denke mit setCounterdu meinstsetCount
Mehdi Dehghani
15

useEffect, Dass nur Brände auf Zustand Updates :

const [state, setState] = useState({ name: "Michael" })
const isFirstRender = useRef(true)

useEffect(() => {
  if (!isFirstRender.current) {
    console.log(state) // do something after state has updated
  }
}, [state])

useEffect(() => { 
  isFirstRender.current = false // toggle flag after first render/mounting
}, [])

Oben wird der setStateRückruf am besten nachgeahmt und nicht für den Anfangszustand ausgelöst. Sie können einen benutzerdefinierten Hook daraus extrahieren:

function useEffectUpdate(effect, deps) {
  const isFirstRender = useRef(true) 

  useEffect(() => {
    if (!isFirstRender.current) {
      effect()
    }  
  }, deps) // eslint-disable-line react-hooks/exhaustive-deps

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

// ... Usage inside component
useEffectUpdate(() => { console.log(state) }, [state])
ford04
quelle
Alternative: useState Kann implementiert werden , um einen Rückruf wie setStatein Klassen zu erhalten.
Ford04
15

Ich denke, die Verwendung useEffectist kein intuitiver Weg.

Ich habe dafür einen Wrapper erstellt. In diesem benutzerdefinierten Hook können Sie Ihren Rückruf an setStateParameter anstelle von useStateParameter senden .

Ich habe gerade eine Typescript-Version erstellt. Wenn Sie dies in Javascript verwenden müssen, entfernen Sie einfach eine Typennotation aus dem Code.

Verwendung

const [state, setState] = useStateCallback(1);
setState(2, (n) => {
  console.log(n) // 2
});

Erklärung

import { SetStateAction, useCallback, useEffect, useRef, useState } from 'react';

type Callback<T> = (value?: T) => void;
type DispatchWithCallback<T> = (value: T, callback?: Callback<T>) => void;

function useStateCallback<T>(initialState: T | (() => T)): [T, DispatchWithCallback<SetStateAction<T>>] {
  const [state, _setState] = useState(initialState);

  const callbackRef = useRef<Callback<T>>();
  const isFirstCallbackCall = useRef<boolean>(true);

  const setState = useCallback((setStateAction: SetStateAction<T>, callback?: Callback<T>): void => {
    callbackRef.current = callback;
    _setState(setStateAction);
  }, []);

  useEffect(() => {
    if (isFirstCallbackCall.current) {
      isFirstCallbackCall.current = false;
      return;
    }
    callbackRef.current?.(state);
  }, [state]);

  return [state, setState];
}

export default useStateCallback;

Nachteil

Wenn die übergebene Pfeilfunktion auf eine variable äußere Funktion verweist, erfasst sie den aktuellen Wert und keinen Wert, nachdem der Status aktualisiert wurde. Im obigen Verwendungsbeispiel gibt console.log (Status) 1 statt 2 aus.

MJ Studio
quelle
1
Vielen Dank! cooler Ansatz. Erstellt eine Probe codesandbox
ValYouW
@ValYouW Vielen Dank, dass Sie ein Beispiel erstellt haben. Die einzige Sache ist, dass wenn die übergebene Pfeilfunktion auf die äußere Funktion der Variablen verweist, aktuelle Werte erfasst werden, nicht Werte, nachdem der Status aktualisiert wurde.
MJ Studio
Ja, das ist der eingängige Teil mit funktionalen Komponenten ...
ValYouW
Wie erhalte ich hier den vorherigen Statuswert?
Ankan-Zerob
@ Ankan-Zerob Ich denke, im Usage-Teil statebezieht sich der noch auf den vorherigen, wenn Callback aufgerufen wird. Ist es nicht?
MJ Studio
0

Ihre Frage ist sehr gültig. Lassen Sie mich Ihnen sagen, dass useEffect standardmäßig einmal ausgeführt wird und jedes Mal, wenn sich das Abhängigkeitsarray ändert.

Überprüfen Sie das folgende Beispiel:

import React,{ useEffect, useState } from "react";

const App = () => {
  const [age, setAge] = useState(0);
  const [ageFlag, setAgeFlag] = useState(false);

  const updateAge = ()=>{
    setAgeFlag(false);
    setAge(age+1);
    setAgeFlag(true);
  };

  useEffect(() => {
    if(!ageFlag){
      console.log('effect called without change - by default');
    }
    else{
      console.log('effect called with change ');
    }
  }, [ageFlag,age]);

  return (
    <form>
      <h2>hooks demo effect.....</h2>
      {age}
      <button onClick={updateAge}>Text</button>
    </form>
  );
}

export default App;

Wenn Sie möchten, dass der setState-Rückruf mit den Hooks ausgeführt wird, verwenden Sie die Flag-Variable und geben Sie den IF IF-ODER-IF-Block in useEffect an, damit nur dieser Codeblock ausgeführt wird, wenn diese Bedingungen erfüllt sind. Wie auch immer, der Effekt wird ausgeführt, wenn sich das Abhängigkeitsarray ändert, aber der IF-Code innerhalb des Effekts wird nur unter diesen bestimmten Bedingungen ausgeführt.

Arjun Sah
quelle
3
Das wird nicht funktionieren. Sie wissen nicht, in welcher Reihenfolge die drei Anweisungen in updateAge tatsächlich funktionieren. Alle drei sind asynchron. Das einzige, was garantiert ist, ist, dass die erste Zeile vor der dritten Zeile läuft (da sie im selben Status arbeiten). Sie wissen nichts über die 2. Zeile. Dieses Beispiel ist zu einfach, um dies zu sehen.
Mohit Singh
Mein Freund Mohit. Ich habe diese Technik in einem großen komplexen Reaktionsprojekt implementiert, als ich von Reaktionsklassen zu Hooks wechselte, und sie funktioniert perfekt. Versuchen Sie einfach die gleiche Logik überall in Hooks, um den setState-Rückruf zu ersetzen, und Sie werden es wissen.
Arjun Sah
3
"Arbeiten in meinem Projekt ist keine Erklärung", lesen Sie die Dokumentation. Sie sind überhaupt nicht synchron. Sie können nicht sicher sagen, dass die drei Zeilen in updateAge in dieser Reihenfolge funktionieren würden. Wenn es synchron war, was Flag benötigt, rufen Sie die Zeile console.log () direkt nach setAge auf.
Mohit Singh
useRef ist eine viel bessere Lösung für "ageFlag".
Dr.Flink
0

setState() Änderungen am Komponentenstatus in die Warteschlange stellen und React mitteilen, dass diese Komponente und ihre untergeordneten Komponenten mit dem aktualisierten Status neu gerendert werden müssen.

Die setState-Methode ist asynchron und gibt in der Tat kein Versprechen zurück. In Fällen, in denen eine Funktion aktualisiert oder aufgerufen werden soll, kann die Funktion in der setState-Funktion als zweites Argument als Rückruf bezeichnet werden. In Ihrem obigen Fall haben Sie beispielsweise eine Funktion als setState-Rückruf aufgerufen.

setState(
  { name: "Michael" },
  () => console.log(this.state)
);

Der obige Code funktioniert gut für Klassenkomponenten, aber im Fall von Funktionskomponenten können wir die setState-Methode nicht verwenden, und dies können wir den use-Effekt-Hook verwenden, um das gleiche Ergebnis zu erzielen.

Die offensichtliche Methode, die in den Sinn kommt, ist, dass ypu mit useEffect verwendet werden kann:

const [state, setState] = useState({ name: "Michael" })

useEffect(() => {
  console.log(state) // do something after state has updated
}, [state])

Dies würde jedoch auch beim ersten Rendern ausgelöst, sodass wir den Code wie folgt ändern können, um das erste Renderereignis zu überprüfen und das Status-Rendering zu vermeiden. Daher kann die Implementierung folgendermaßen erfolgen:

Wir können den Benutzer-Hook hier verwenden, um das erste Rendering zu identifizieren.

Mit dem useRef-Hook können wir veränderbare Variablen in Funktionskomponenten erstellen. Dies ist nützlich, um auf DOM-Knoten / React-Elemente zuzugreifen und veränderbare Variablen zu speichern, ohne ein erneutes Rendern auszulösen.

const [state, setState] = useState({ name: "Michael" });
const firstTimeRender = useRef(true);

useEffect(() => {
 if (!firstTimeRender.current) {
    console.log(state);
  }
}, [state])

useEffect(() => { 
  firstTimeRender.current = false 
}, [])
Laura Nutt
quelle
0

Ich hatte einen Anwendungsfall, in dem ich einen API-Aufruf mit einigen Parametern tätigen wollte, nachdem der Status festgelegt wurde. Ich wollte diese Parameter nicht als meinen Status festlegen, also habe ich einen benutzerdefinierten Hook erstellt und hier ist meine Lösung

import { useState, useCallback, useRef, useEffect } from 'react';
import _isFunction from 'lodash/isFunction';
import _noop from 'lodash/noop';

export const useStateWithCallback = initialState => {
  const [state, setState] = useState(initialState);
  const callbackRef = useRef(_noop);

  const handleStateChange = useCallback((updatedState, callback) => {
    setState(updatedState);
    if (_isFunction(callback)) callbackRef.current = callback;
  }, []);

  useEffect(() => {
    callbackRef.current();
    callbackRef.current = _noop; // to clear the callback after it is executed
  }, [state]);

  return [state, handleStateChange];
};
Akash Singh
quelle
-3

UseEffect ist die primäre Lösung. Wie Darryl bereits erwähnt hat, wird die Komponente beim Initialisierungsprozess ausgeführt, wenn useEffect verwendet und der Status als zweiter Parameter übergeben wird. Wenn Sie nur möchten, dass die Rückruffunktion mit dem Wert des aktualisierten Status ausgeführt wird, können Sie eine lokale Konstante festlegen und diese sowohl im setState als auch im Rückruf verwenden.

const [counter, setCounter] = useState(0);

const doSomething = () => {
  const updatedNumber = 123;
  setCounter(updatedNumber);

  // now you can "do something" with updatedNumber and don't have to worry about the async nature of setState!
  console.log(updatedNumber);
}
Kyle Laster
quelle
4
setCounter ist asynchron. Sie wissen nicht, ob console.log nach setCounter oder früher aufgerufen wird.
Mohit Singh