React中如何防止keypress事件监听器导致state变量先true后false
问题根因
- 你的代码没有正确管理键盘事件监听器的生命周期:组件每触发一次重渲染就会给window新增一个keypress监听回调,这些回调不会自动清除,每个回调里捕获的都是当前渲染闭包里的
shout、message变量的旧值,所以你修改shout后,不同版本的回调会打印不同的shout值,最终导致逻辑混乱。 - 原生事件回调内获取的React state是创建回调时闭包缓存的旧值,默认无法拿到最新的state状态。
解决方案
方案1:用useEffect管理事件监听的绑定/销毁,配合state函数式更新
该方案符合React生命周期管理逻辑,代码改动最小:
import { useState, useEffect } from "react"; const App = () => { const [shout, setShout] = useState(true); const [message, setMessage] = useState("hello world!"); useEffect(() => { const handleKeypress = e => { console.log(shout); if(e.key === "a" && shout) { // 函数式更新直接读取最新的message值,无需将message加入依赖列表 setMessage(prevMsg => prevMsg.toUpperCase()) } }; window.addEventListener("keypress", handleKeypress); // 组件卸载/依赖变更时清除旧的监听,避免重复绑定 return () => window.removeEventListener("keypress", handleKeypress); }, [shout]); // 仅shout变更时才重新绑定监听 return ( <div> <h1>{message}</h1> <button onClick={() => setShout(!shout)}>disable keypress!</button> <button onClick={() => setMessage(prevMsg => prevMsg.toLowerCase())}>to lower case</button> </div> ) } export default App;
方案2:用useRef存储最新状态,避免频繁重新绑定监听
如果不想每次shout变更都重新绑定监听,可以用ref存储最新的state值,回调内直接读取ref的current属性即可:
import { useState, useEffect, useRef } from "react"; const App = () => { const [shout, setShout] = useState(true); const [message, setMessage] = useState("hello world!"); // 用ref存储最新的state值 const shoutRef = useRef(shout); const messageRef = useRef(message); // state变更时同步更新ref的值 useEffect(() => { shoutRef.current = shout; messageRef.current = message; }, [shout, message]); useEffect(() => { const handleKeypress = e => { console.log(shoutRef.current); if(e.key === "a" && shoutRef.current) { setMessage(messageRef.current.toUpperCase()) } }; window.addEventListener("keypress", handleKeypress); return () => window.removeEventListener("keypress", handleKeypress); }, []); // 仅组件挂载时绑定一次,卸载时销毁 return ( <div> <h1>{message}</h1> <button onClick={() => setShout(!shout)}>disable keypress!</button> <button onClick={() => setMessage(prevMsg => prevMsg.toLowerCase())}>to lower case</button> </div> ) } export default App;
内容的提问来源于stack exchange,提问作者thierved
相关产品推荐
相关产品推荐

