Ich verfolge einen Udemy-Kurs zum Registrieren von Ereignissen mit Hooks. Der Kursleiter gab den folgenden Code an:
const [userText, setUserText] = useState('');
const handleUserKeyPress = event => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(`${userText}${key}`);
}
};
useEffect(() => {
window.addEventListener('keydown', handleUserKeyPress);
return () => {
window.removeEventListener('keydown', handleUserKeyPress);
};
});
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
Jetzt funktioniert es großartig, aber ich bin nicht davon überzeugt, dass dies der richtige Weg ist. Der Grund dafür ist, dass, wenn ich das richtig verstehe, Ereignisse bei jedem erneuten Rendern jedes Mal registriert und abgemeldet werden, und ich denke einfach nicht, dass dies der richtige Weg ist, dies zu tun.
Also habe ich die useEffect
Haken unten leicht modifiziert
useEffect(() => {
window.addEventListener('keydown', handleUserKeyPress);
return () => {
window.removeEventListener('keydown', handleUserKeyPress);
};
}, []);
Indem Sie ein leeres Array als zweites Argument verwenden und die Komponente den Effekt nur einmal ausführen lassen, um zu imitieren componentDidMount
. Und wenn ich das Ergebnis ausprobiere, ist es seltsam, dass bei jedem Schlüssel, den ich eingebe, statt angehängt, stattdessen überschrieben wird.
Ich hatte setUserText ( ${userText}${key}
) erwartet ; Wenn ein neuer Schlüssel an den aktuellen Status angehängt und als neuer Status festgelegt wird, wird der alte Status vergessen und mit dem neuen Status neu geschrieben.
War es wirklich die richtige Art und Weise, dass wir Ereignisse bei jedem erneuten Rendern registrieren und abmelden sollten?
prevUserText
auf den vorherigen Zustand von zu verweisenuserText
. Was ist, wenn ich auf mehrere Status zugreifen muss? Wie kann ich auf alle vorherigen Status zugreifen?useReducer
anstelle von verwendenuseState
?Problem
Du hast recht. Es ist nicht sinnvoll, die Ereignisbehandlung
useEffect
bei jedem Rendern neu zu starten .Dies ist ein Problem mit veralteten Schließwerten .
Grund: Verwendete Funktionen im Inneren
useEffect
sollten Teil der Abhängigkeiten sein . Sie setzen nichts als dependency ([]
), rufen aber trotzdem aufhandleUserKeyPress
, der selbstuserText
state liest .Lösung
Abhängig von Ihrem Anwendungsfall gibt es einige Alternativen.
1. Statusaktualisierungsfunktion
setUserText(prev => `${prev}${key}`);
✔ am wenigsten invasiver Ansatz
✖ nur Zugriff auf den eigenen vorherigen Zustand, nicht auf andere Zustände
Code-Snippet anzeigen
const App = () => { const [userText, setUserText] = useState(""); useEffect(() => { const handleUserKeyPress = event => { const { key, keyCode } = event; if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) { setUserText(prev => `${prev}${key}`); // use updater function here } }; window.addEventListener("keydown", handleUserKeyPress); return () => { window.removeEventListener("keydown", handleUserKeyPress); }; }, []); // still no dependencies return ( <div> <h1>Feel free to type!</h1> <blockquote>{userText}</blockquote> </div> ); } ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script> <div id="root"></div> <script>var { useReducer, useEffect, useState, useRef } = React</script>
2.
useReducer
- "Cheat-Modus"Wir können
Variante 2a: Logik innerhalb der ReduzierfunktionuseReducer
zum aktuellen Status / Requisiten wechseln und Zugriff darauf haben - mit ähnlicher API wieuseState
.const [userText, handleUserKeyPress] = useReducer((state, event) => { const { key, keyCode } = event; // isUpperCase is always the most recent state (no stale closure value) return `${state}${isUpperCase ? key.toUpperCase() : key}`; }, "");
Code-Snippet anzeigen
const App = () => { const [isUpperCase, setUpperCase] = useState(false); const [userText, handleUserKeyPress] = useReducer((state, event) => { const { key, keyCode } = event; if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) { // isUpperCase is always the most recent state (no stale closure) return `${state}${isUpperCase ? key.toUpperCase() : key}`; } }, ""); useEffect(() => { window.addEventListener("keydown", handleUserKeyPress); return () => { window.removeEventListener("keydown", handleUserKeyPress); }; }, []); return ( <div> <h1>Feel free to type!</h1> <blockquote>{userText}</blockquote> <button style={{ width: "150px" }} onClick={() => setUpperCase(b => !b)}> {isUpperCase ? "Disable" : "Enable"} Upper Case </button> </div> ); } ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script> <div id="root"></div> <script>var { useReducer, useEffect, useState, useRef } = React</script>
useState
Updater-Funktionconst [userText, setUserText] = useReducer((state, action) => typeof action === "function" ? action(state, isUpperCase) : action, ""); // ... setUserText((prevState, isUpper) => `${prevState}${isUpper ? key.toUpperCase() : key}`);
Code-Snippet anzeigen
const App = () => { const [isUpperCase, setUpperCase] = useState(false); const [userText, setUserText] = useReducer( (state, action) => typeof action === "function" ? action(state, isUpperCase) : action, "" ); useEffect(() => { const handleUserKeyPress = event => { const { key, keyCode } = event; if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) { setUserText( (prevState, isUpper) => `${prevState}${isUpper ? key.toUpperCase() : key}` ); } }; window.addEventListener("keydown", handleUserKeyPress); return () => { window.removeEventListener("keydown", handleUserKeyPress); }; }, []); return ( <div> <h1>Feel free to type!</h1> <blockquote>{userText}</blockquote> <button style={{ width: "150px" }} onClick={() => setUpperCase(b => !b)}> {isUpperCase ? "Disable" : "Enable"} Upper Case </button> </div> ); } ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script> <div id="root"></div> <script>var { useReducer, useEffect, useState, useRef } = React</script>
✔ keine Notwendigkeit , um Abhängigkeiten zu verwalten
✔ Zugriff mehrere Zustände und Requisiten
✔ gleiche API wie
useState
✔ erweiterbar auf komplexere Fälle / Reduktions
✖ etwas weniger Leistung durch Inline - Reduzierstück ( irgendwie vernachlässigbaren )
✖ leicht erhöhte Komplexität Minderer
3.
useCallback
/ event handler insideuseEffect
Wenn Sie nach
handleUserKeyPress
innen gehenuseEffect
, zeigt Ihnen die ESLint-Regel für umfassende Deps, welche genauen kanonischen Abhängigkeiten fehlen (userText
):Code-Snippet anzeigen
const App =() => { const [userText, setUserText] = useState(""); useEffect(() => { const handleUserKeyPress = event => { const { key, keyCode } = event; if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) { setUserText(`${userText}${key}`); } }; window.addEventListener("keydown", handleUserKeyPress); return () => { window.removeEventListener("keydown", handleUserKeyPress); }; }, [userText]); // ESLint will yell here, if `userText` is missing return ( <div> <h1>Feel free to type!</h1> <blockquote>{userText}</blockquote> </div> ); } ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script> <div id="root"></div> <script>var { useReducer, useEffect, useState, useRef } = React</script>
useCallback
ist eine äquivalente Alternative mit etwas mehr Indirektion von Abhängigkeiten:Code-Snippet anzeigen
const App = () => { const [userText, setUserText] = useState(""); const handleUserKeyPress = useCallback( event => { const { key, keyCode } = event; if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) { setUserText(`${userText}${key}`); } }, [userText] ); useEffect(() => { window.addEventListener("keydown", handleUserKeyPress); return () => { window.removeEventListener("keydown", handleUserKeyPress); }; }, [handleUserKeyPress]); // we rely directly on handler, indirectly on userText return ( <div> <h1>Feel free to type!</h1> <blockquote>{userText}</blockquote> </div> ); } ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script> <div id="root"></div> <script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
Hinweis: Diese Variante kann zwar auf verschiedene Arten angewendet werden, ist jedoch nicht für den Fragenfall geeignet und der Vollständigkeit halber aufgeführt. Grund: Der Ereignis-Listener wird bei jedem Tastendruck neu gestartet.
✔ pragmatische Allzwecklösung
✔ minimal invasiv
✖ manuelles Abhängigkeitsmanagement
✖
useCallback
macht die Funktionsdefinition ausführlicher / übersichtlicher4.
useRef
/ Rückruf in veränderlicher Referenz speichernconst cbRef = useRef(handleUserKeyPress); useEffect(() => { cbRef.current = handleUserKeyPress; }); // update after each render useEffect(() => { const cb = e => cbRef.current(e); // then use most recent cb value window.addEventListener("keydown", cb); return () => { window.removeEventListener("keydown", cb) }; }, []);
Code-Snippet anzeigen
const App = () => { const [userText, setUserText] = useState(""); const handleUserKeyPress = event => { const { key, keyCode } = event; if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) { setUserText(`${userText}${key}`); } }; const cbRef = useRef(handleUserKeyPress); useEffect(() => { cbRef.current = handleUserKeyPress; }); useEffect(() => { const cb = e => cbRef.current(e); window.addEventListener("keydown", cb); return () => { window.removeEventListener("keydown", cb); }; }, []); return ( <div> <h1>Feel free to type!</h1> <blockquote>{userText}</blockquote> </div> ); } ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script> <div id="root"></div> <script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
✔ für Rückrufe / Ereignishandler, die nicht zum erneuten Rendern des Datenflusses verwendet werden.
✔ Keine Notwendigkeit, Abhängigkeiten zu verwalten
✖ Nur als letzte Option von React Docs
empfohlen. ✖ Wichtigerer Ansatz
Weitere Informationen finden Sie unter diesen Links: 1 2 3
quelle
neue Antwort:
useEffect(() => { function handlekeydownEvent(event) { const { key, keyCode } = event; if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) { setUserText(prevUserText => `${prevUserText}${key}`); } } document.addEventListener('keyup', handlekeydownEvent) return () => { document.removeEventListener('keyup', handlekeydownEvent) } }, [])
Übergeben Sie bei Verwendung
setUserText
die Funktion als Argument anstelle des Objekts.prevUserText
Dies ist immer der neueste Status.alte Antwort:
Versuchen Sie dies, es funktioniert genauso wie Ihr ursprünglicher Code:
useEffect(() => { function handlekeydownEvent(event) { const { key, keyCode } = event; if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) { setUserText(`${userText}${key}`); } } document.addEventListener('keyup', handlekeydownEvent) return () => { document.removeEventListener('keyup', handlekeydownEvent) } }, [userText])
weil es in Ihrer
useEffect()
Methode von deruserText
Variablen abhängt , aber Sie es nicht in das zweite Argument einfügen, sonstuserText
wird das immer''
mit dem Argument an den Anfangswert gebunden[]
.Sie müssen dies nicht tun, sondern möchten Sie nur wissen lassen, warum Ihre zweite Lösung nicht funktioniert.
quelle
[userText]
ist genau das gleiche wie ohne zweites Argument, oder? Grund dafür ist, dass ich nuruserText
im obigen Beispiel habe und ohne zweites Argument einfach bedeutet, bei jeder Änderung von Requisiten / Status erneut zu rendern. Ich sehe nicht, wie es meine Frage beantwortet. ** P / S: ** Ich bin nicht der Downvoter, trotzdem danke für deine AntwortuseEffect()
von deruserText
Variablen abhängt, Sie aber die zweiten Argumente nicht eingegeben haben.[userText]
, bedeutet dies auch, dass Sie das Ereignis bei jedem erneuten Rendern registrieren und abmelden müssen, oder?useRef
genau wie die Antwort von @Maaz Syed Adeeb verwenden.Benötigt für Ihren Anwendungsfall
useEffect
ein Abhängigkeitsarray, um Änderungen zu verfolgen, und basierend auf der Abhängigkeit kann bestimmt werden, ob erneut gerendert werden soll oder nicht. Es wird immer empfohlen, ein Abhängigkeitsarray an zu übergebenuseEffect
. Bitte beachten Sie den folgenden Code:Ich habe
useCallback
Haken eingeführt .const { useCallback, useState, useEffect } = React; const [userText, setUserText] = useState(""); const handleUserKeyPress = useCallback(event => { const { key, keyCode } = event; if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) { setUserText(prevUserText => `${prevUserText}${key}`); } }, []); useEffect(() => { window.addEventListener("keydown", handleUserKeyPress); return () => { window.removeEventListener("keydown", handleUserKeyPress); }; }, [handleUserKeyPress]); return ( <div> <blockquote>{userText}</blockquote> </div> );
quelle
[userText]
oder ohne zweites Argument. Grundsätzlich setzen wir einconsole.log
InnenuseEffect
, wir werden sehen, dass die Protokollierung jedes Re-Rendering auslöst, was auch bedeutet, dassaddEventListender
jedes Re-Rendering ausgeführt wirdconsole.log('>');
inuseEffect
Hooks eingefügt. Wenn Sie Ihren aktualisierten Code verwenden, wird dieser immer noch protokolliert. Dies bedeutet auch, dass die Ereignisse bei jedem erneuten Rendern registriert werdenreturn () => {window.removeEventListener('keydown', handleUserKeyPress)}
, bei jedem erneuten Rendern wird die Komponente registriert und abgemeldetSie benötigen eine Möglichkeit, den vorherigen Status zu verfolgen.
useState
hilft Ihnen, nur den aktuellen Status zu verfolgen. In den Dokumenten können Sie mithilfe eines anderen Hooks auf den alten Status zugreifen.const prevRef = useRef(); useEffect(() => { prevRef.current = userText; });
Ich habe Ihr Beispiel aktualisiert, um dies zu verwenden. Und es klappt.
const { useState, useEffect, useRef } = React; const App = () => { const [userText, setUserText] = useState(""); const prevRef = useRef(); useEffect(() => { prevRef.current = userText; }); const handleUserKeyPress = event => { const { key, keyCode } = event; if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) { setUserText(`${prevRef.current}${key}`); } }; useEffect(() => { window.addEventListener("keydown", handleUserKeyPress); return () => { window.removeEventListener("keydown", handleUserKeyPress); }; }, []); return ( <div> <h1>Feel free to type!</h1> <blockquote>{userText}</blockquote> </div> ); }; ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script> <div id="root"></div>
quelle
Beim zweiten Ansatz wird das
useEffect
nur einmal gebunden und daher wird dasuserText
nie aktualisiert. Ein Ansatz wäre, eine lokale Variable beizubehalten, dieuserText
bei jedem Tastendruck zusammen mit dem Objekt aktualisiert wird.const [userText, setUserText] = useState(''); let local_text = userText const handleUserKeyPress = event => { const { key, keyCode } = event; if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) { local_text = `${userText}${key}`; setUserText(local_text); } }; useEffect(() => { window.addEventListener('keydown', handleUserKeyPress); return () => { window.removeEventListener('keydown', handleUserKeyPress); }; }, []); return ( <div> <h1>Feel free to type!</h1> <blockquote>{userText}</blockquote> </div> );
Persönlich mag ich die Lösung nicht, fühle
anti-react
mich und ich denke, die erste Methode ist gut genug und wurde entwickelt, um auf diese Weise verwendet zu werden.quelle
Sie haben keinen Zugriff auf den geänderten useText-Status. Sie können es dem vorherigen Status zuordnen. Speichern Sie den Zustand in einer Variablen, zB: Zustand wie folgt:
const App = () => { const [userText, setUserText] = useState(''); useEffect(() => { let state = '' const handleUserKeyPress = event => { const { key, keyCode } = event; if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) { state += `${key}` setUserText(state); } }; window.addEventListener('keydown', handleUserKeyPress); return () => { window.removeEventListener('keydown', handleUserKeyPress); }; }, []); return ( <div> <h1>Feel free to type!</h1> <blockquote>{userText}</blockquote> </div> ); };
quelle