Was ist die Verwendung der funktionalen Syntax von setState, um funktionale Komponenten zu reagieren? [Duplikat]
wir sprechen über funktionale Komponenten mit useState
sagen wir
const [age, setAge] = useState(0)
Lassen Sie uns jetzt beim Aktualisieren sagen, dass age
ich die vorherige verwenden mussage
React-Dokumente erwähnen etwas, das als FUNCTIONAL UPDATES bezeichnet wird, bei dem Sie eine Funktion übergeben können, und das Argument dafür ist der vorherige Wert des Status, z.
setState((previousAge) => previousAge + 1)
Warum muss ich das tun, wenn ich es einfach kann?
setState(previousAge + 1)
Was sind die Vorteile der Verwendung von funktionalen setState
,
Ich weiß, dass es in klassenbasierten Komponenten so etwas wie das Stapeln von Statusaktualisierungen auf funktionale Weise gab, aber ich kann so etwas in der Dokumentation der funktionalen Komponenten nicht finden.
Antworten
Sie sind nicht gleich. Wenn Ihr Update von einem vorherigen Wert im Status abhängt, sollten Sie das Funktionsformular verwenden. Wenn Sie in diesem Fall das Funktionsformular nicht verwenden, wird Ihr Code irgendwann beschädigt.
Warum bricht es und wann
React-Funktionskomponenten sind nur Abschlüsse. Der Statuswert, den Sie im Closure haben, ist möglicherweise veraltet. Dies bedeutet, dass der Wert im Closure nicht mit dem Wert übereinstimmt, der sich im React-Status für diese Komponente befindet folgende Fälle:
1- asynchrone Vorgänge ( In diesem Beispiel klicken Sie auf langsames Hinzufügen und dann mehrmals auf die Schaltfläche Hinzufügen. Später sehen Sie, dass der Status auf das zurückgesetzt wurde, was sich innerhalb des Abschlusses befand, als auf die Schaltfläche langsames Hinzufügen geklickt wurde.)
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
}}
>
immediately add
</button>
<button
onClick={() => {
setTimeout(() => setCounter(counter + 1), 1000);
}}
>
Add
</button>
</>
);
};
2- Wenn Sie die Aktualisierungsfunktion mehrmals im selben Abschluss aufrufen
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
setCounter(counter + 1);
}}
>
Add twice
</button>
</>
);
}
Abhängig davon, wie schnell / oft Ihr Setter angerufen wird, können Probleme auftreten.
Wenn Sie auf einfache Weise den Wert aus dem Abschluss abrufen, haben nachfolgende Aufrufe zwischen zwei Renderings möglicherweise nicht den gewünschten Effekt.
Ein einfaches Beispiel:
function App() {
const [counter, setCounter] = useState(0);
const incWithClosure = () => {
setCounter(counter + 1);
};
const incWithUpdate = () => {
setCounter(oldCounter => oldCounter + 1);
};
return (<>
<button onClick={_ => { incWithClosure(); incWithClosure(); }}>
Increment twice using incWithClosure
</button>
<button onClick={_ => { incWithUpdate(); incWithUpdate(); }}>
Increment twice using incWithUpdate
</button>
<p>{counter}</p>
</>);
}
Beide Schaltflächen rufen eine der Inkrementierungsmethoden zweimal auf. Aber wir beobachten:
- Die erste Taste erhöht den Zähler nur um 1
- Die zweite Taste erhöht den Zähler um 2, was wahrscheinlich das gewünschte Ergebnis ist.
Wann kann das passieren?
- Offensichtlich, wenn
incWithClosure
mehrmals unmittelbar nacheinander aufgerufen wird - Wenn asynchrone Aufgaben beteiligt sind, kann dies leicht passieren (siehe unten).
- Wenn React viel zu tun hat, entscheiden sich die Planungsalgorithmen möglicherweise dafür, mehrere sehr schnelle Klicks mit demselben Ereignishandler zu verarbeiten
Beispiel mit asynchroner Arbeit (Simulation des Ladens einer Ressource):
function App() {
const [counter, setCounter] = useState(0);
const incWithClosureDelayed = () => {
setTimeout(() => {
setCounter(counter + 1);
}, 1000);
};
const incWithUpdateDelayed = () => {
setTimeout(() => {
setCounter((oldCounter) => oldCounter + 1);
}, 1000);
};
return (
<>
<button onClick={(_) => incWithClosureDelayed()}>
Increment slowly using incWithClosure
</button>
<button onClick={(_) => incWithUpdateDelayed()}>
Increment slowly using incWithUpdate
</button>
<p>{counter}</p>
</>
);
}
Klicken Sie zweimal (innerhalb einer Sekunde) auf die erste Schaltfläche und stellen Sie fest, dass der Zähler nur um 1 erhöht wird. Die zweite Schaltfläche hat das richtige Verhalten.
Denn wenn Sie dies nicht tun, werden Sie irgendwann feststellen, dass Sie einen alten Wert für erhalten age
. Das Problem ist, manchmal funktioniert das, was Sie vorschlagen,. Aber manchmal wird es nicht. Möglicherweise wird Ihr aktueller Code heute nicht beschädigt, aber möglicherweise ein anderer Code, den Sie vor einigen Wochen geschrieben haben, oder Ihr aktueller Code in einigen Monaten.
Das Symptom ist wirklich sehr, sehr seltsam. Sie können den Wert der Variablen innerhalb einer jsx-Komponente mithilfe der {x}
Syntax drucken und später dieselbe Variable console.log
nach dem Rendern der jsx-Komponente (nicht vorher) drucken und feststellen, dass der console.log
Wert veraltet ist - der Wert , der nach dem Rendern auftritt console.log
, kann einen älteren Wert haben als das Rendern.
Daher funktioniert der tatsächliche Wert von Statusvariablen im regulären Code möglicherweise nicht immer korrekt. Sie sind nur so konzipiert, dass sie den neuesten Wert in einem Rendering zurückgeben. Aus diesem Grund wurde der Rückrufmechanismus in einem Statussetzer implementiert, damit Sie den neuesten Wert einer Statusvariablen in regulärem Code außerhalb eines Renderings abrufen können.