Problem
[...] Bei jedem erneuten Rendern werden Ereignisse jedes Mal registriert und abgemeldet, und ich denke einfach nicht, dass dies der richtige Weg ist.
Du hast recht. Es ist nicht sinnvoll, die Ereignisbehandlung useEffect
bei jedem Rendern neu zu starten .
[...] leeres Array als zweites Argument, sodass die Komponente den Effekt nur einmal ausführen kann, [...] es ist seltsam, dass bei jedem Schlüssel, den ich eingebe, statt angehängt, stattdessen überschrieben wird.
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 auf handleUserKeyPress
, der selbst userText
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
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}`);
}
};
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://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
Wir können useReducer
zum aktuellen Status / Requisiten wechseln und Zugriff darauf haben - mit ähnlicher API wie useState
.
Variante 2a: Logik innerhalb der Reduzierfunktion
const [userText, handleUserKeyPress] = useReducer((state, event) => {
const { key, keyCode } = event;
return `${state}${isUpperCase ? key.toUpperCase() : key}`;
}, "");
const App = () => {
const [isUpperCase, setUpperCase] = useState(false);
const [userText, handleUserKeyPress] = useReducer((state, event) => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
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/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
Variante 2b: Logik außerhalb der Reduzierfunktion - ähnlich der
useState
Updater-Funktion
const [userText, setUserText] = useReducer((state, action) =>
typeof action === "function" ? action(state, isUpperCase) : action, "");
setUserText((prevState, isUpper) => `${prevState}${isUpper ? key.toUpperCase() : key}`);
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/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/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 gehen useEffect
, zeigt Ihnen die ESLint-Regel für umfassende Deps, welche genauen kanonischen Abhängigkeiten fehlen ( userText
):
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]);
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/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:
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]);
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/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 / übersichtlicher
4. useRef
/ Rückruf in veränderlicher Referenz speichern
const cbRef = useRef(handleUserKeyPress);
useEffect(() => { cbRef.current = handleUserKeyPress; });
useEffect(() => {
const cb = e => cbRef.current(e);
window.addEventListener("keydown", cb);
return () => { window.removeEventListener("keydown", cb) };
}, []);
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/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/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