In diesem Beitrag zeige ich, wie man einen Abschluss in einer useState-Hook-React-App erstellt.
Ich werde nicht erklären, was ein Abschluss ist, da es viele Ressourcen zu diesem Thema gibt und ich mich nicht wiederholen möchte. Ich empfehle die Lektüre dieses Artikels von @imranabdulmalik.
Kurz gesagt ist ein Abschluss (von Mozilla):
...die Kombination einer gebündelten (eingeschlossenen) Funktion mit Verweisen auf ihren umgebenden Zustand (die lexikalische Umgebung). Mit anderen Worten, ein Abschluss ermöglicht Ihnen den Zugriff auf den Umfang einer äußeren Funktion von einer inneren Funktion aus. In JavaScript werden Abschlüsse jedes Mal erstellt, wenn eine Funktion zum Zeitpunkt der Funktionserstellung erstellt wird.
Falls Sie mit dem Begriff „lexikalische Umgebung nicht vertraut sind, können Sie diesen Artikel von @soumyadey oder alternativ diesen hier lesen.
In einer React-Anwendung können Sie versehentlich einen Abschluss einer Variablen erstellen, die zum Komponentenstatus gehört, der mit dem useState-Hook erstellt wurde. Wenn dies geschieht, stehen Sie vor einem Problem des veralteten Abschlusses, das heißt, wenn Sie auf einen alten Wert des Zustands verweisen, der sich inzwischen geändert hat und daher nicht relevanter ist.
Ich habe eine Demo-React-Anwendung erstellt, deren Hauptziel darin besteht, einen Zähler (der zum Status gehört) zu erhöhen, der in einem Abschluss im Rückruf der setTimeout-Methode geschlossen werden kann.
Kurz gesagt, diese App kann:
Im folgenden Bild wird der anfängliche UI-Status der App angezeigt, mit Zähler auf Null.
Wir simulieren das Schließen des Zählers in drei Schritten:
Nach 5 Sekunden beträgt der Wert des Zählers 2.
Der erwartete Wert des Zählers sollte 12 sein, aber wir erhalten 2.
Der Grund, warum dies geschieht, liegt darin, dass wir im an setTimeout übergebenen Rückruf einen Abschluss des Zählers erstellt haben und den Zähler bei Auslösung des Timeouts beginnend mit dessen Wert festlegen alter Wert (das war 1).
setTimeout(() => { setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`]) setTimeoutInProgress(false) setStartTimeout(false) setCounter(counter 1) setLogs((l) => [...l, `Did you create a closure of counter?`]) }, timeOutInSeconds * 1000);
Es folgt der vollständige Code der App-Komponente.
function App() { const [counter, setCounter] = useState(0) const timeOutInSeconds: number = 5 const [startTimeout, setStartTimeout] = useState (false) const [timeoutInProgress, setTimeoutInProgress] = useState (false) const [logs, setLogs] = useState >([]) useEffect(() => { if (startTimeout && !timeoutInProgress) { setTimeoutInProgress(true) setLogs((l) => [...l, `Timeout scheduled in ${timeOutInSeconds} seconds`]) setTimeout(() => { setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`]) setTimeoutInProgress(false) setStartTimeout(false) setCounter(counter 1) setLogs((l) => [...l, `Did you create a closure of counter?`]) }, timeOutInSeconds * 1000); } }, [counter, startTimeout, timeoutInProgress]) function renderLogs(): React.ReactNode { const listItems = logs.map((log, index) => {log} ); return{listItems}
; } function updateCounter(value: number) { setCounter(value) setLogs([...logs, `The value of counter is now ${value}`]) } function reset() { setCounter(0) setLogs(["reset done!"]) } return (); } export default App;Closure demo
Counter value: {counter}
Follow the istructions to create a closure of the state variable counter
- Set the counter to preferred value
- Start a timeout and wait for {timeOutInSeconds} to increment the counter (current value is {counter})
- Increment by 10 the counter before the timeout
{ renderLogs() }
Die Lösung basiert auf der Verwendung des useRef-Hooks, mit dem Sie auf einen Wert verweisen können, der für das Rendern nicht benötigt wird.
Also fügen wir der App-Komponente Folgendes hinzu:
const currentCounter = useRef(counter)
Dann ändern wir den Rückruf von setTimeout wie unten gezeigt:
setTimeout(() => { setLogs((l) => [...l, `You closed counter with value: ${currentCounter.current}\n and now I'll increment by one. Check the state`]) setTimeoutInProgress(false) setStartTimeout(false) setCounter(currentCounter.current 1) setLogs((l) => [...l, `Did you create a closure of counter?`]) }, timeOutInSeconds * 1000);
Unser Rückruf muss den Zählerwert lesen, da wir den aktuellen Wert vorher protokollieren, um ihn zu erhöhen.
Falls Sie den Wert nicht lesen müssen, können Sie das Schließen des Zählers vermeiden, indem Sie einfach die funktionale Notation verwenden, um den Zähler zu aktualisieren.
seCounter(c => c 1)
Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.
Copyright© 2022 湘ICP备2022001581号-3