Je suis un cours Udemy sur la façon d'enregistrer des événements avec des hooks, l'instructeur a donné le code ci-dessous:
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>
);
Maintenant, cela fonctionne très bien, mais je ne suis pas convaincu que ce soit la bonne façon. La raison en est, si je comprends bien, à chaque re-rendu, les événements continueront de s'inscrire et de se désenregistrer à chaque fois et je ne pense tout simplement pas que ce soit la bonne façon de procéder.
J'ai donc apporté une légère modification aux crochets useEffect
ci-dessous
useEffect(() => {
window.addEventListener('keydown', handleUserKeyPress);
return () => {
window.removeEventListener('keydown', handleUserKeyPress);
};
}, []);
En ayant un tableau vide comme deuxième argument, permettant au composant d'exécuter l'effet une seule fois, en imitant componentDidMount
. Et quand j'essaie le résultat, c'est bizarre que sur chaque touche que je tape, au lieu de l'ajouter, elle soit remplacée à la place.
Je m'attendais à setUserText (${userText}${key}
); pour que la nouvelle clé tapée soit ajoutée à l'état actuel et définie comme un nouvel état, mais à la place, elle oublie l'ancien état et réécrit avec le nouvel état.
Était-ce vraiment la bonne façon d'enregistrer et de désenregistrer l'événement à chaque re-rendu?
La meilleure façon de gérer de tels scénarios est de voir ce que vous faites dans le gestionnaire d'événements. Si vous définissez simplement l'état en utilisant l'état précédent, il est préférable d'utiliser le modèle de rappel et d'enregistrer les écouteurs d'événements uniquement lors du montage initial. Si vous n'utilisez pas le callback pattern
( https://reactjs.org/docs/hooks-reference.html#usecallback ) la référence des écouteurs avec sa portée lexicale est utilisée par l'écouteur d'événements mais une nouvelle fonction est créée avec fermeture mise à jour sur le nouveau rendu et donc dans le gestionnaire vous ne pourrez pas l'état mis à jour
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>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
nouvelle réponse:
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)
}
}, [])
lorsque vous utilisez setUserText
, passez la fonction comme argument au lieu de l'objet, prevUserText
sera toujours le dernier état.
ancienne réponse:
essayez ceci, il fonctionne de la même manière que votre code d'origine:
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])
car dans votre méthode useEffect()
, cela dépend de la variable userText
mais vous ne la placez pas dans le deuxième argument, sinon le userText
sera toujours lié à l'initiale valeur ''
avec l'argument []
.
vous n'avez pas besoin de faire comme ça, je veux juste vous faire savoir pourquoi votre deuxième solution ne fonctionne pas.
Pour votre cas d'utilisation, useEffect
a besoin d'un tableau de dépendances pour suivre les modifications et en fonction de la dépendance, il peut déterminer s'il faut restituer ou non. Il est toujours conseillé de passer un tableau de dépendances à useEffect
. Veuillez voir le code ci-dessous:
J'ai introduit useCallback
hook.
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>
);
Vous aurez besoin d'un moyen de garder une trace de l'état précédent. useState
vous aide à garder une trace de l'état actuel uniquement. Depuis le docs , il existe un moyen d'accéder à l'ancien état, en utilisant un autre hook.
const prevRef = useRef();
useEffect(() => {
prevRef.current = userText;
});
J'ai mis à jour votre exemple pour l'utiliser. Et ça marche.
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>