React组件绑定keydown事件后按下按键触发两次如何修复
问题修复方案
问题根因
- 事件监听注册位置错误:你将
document.addEventListener直接写在了组件顶层函数体中,React组件每次重渲染都会执行完整的函数体逻辑,相当于每次渲染都会新增一个独立的keydown事件监听,多次渲染后就会存在多个重复回调,按下按键自然会触发多次。 - 事件清理逻辑失效:你在
getKey回调中执行了removeEventListener,但每次组件渲染生成的getKey函数引用都是全新的,你只能移除当前渲染周期注册的getKey,之前渲染周期注册的旧回调无法被清理,会一直残留触发。 - 额外的严格模式挂载:React18 开发环境默认开启严格模式,组件会执行两次挂载逻辑,如果没有正确的监听清理机制,也会导致多注册一次监听。
修复方法
将事件监听的注册和清理逻辑统一放到useEffect中,确保仅在组件挂载时注册一次,卸载时自动清理,同时修正状态直接修改的问题:
const GeneratedKey: FC<IGenerated> = (props) => { const [keyBoard, setKeyBoard] = useState("") const [arrayMovie, setArrayMovie] = useState<string[]>([]) const idPage = props.idpage const nameMovie = data.results[idPage].title const [idLetter, setIdLetter] = useState<number>(0) const [indexArray, setIndexArray] = useState<number[]>([]) const arrayNameMovie = nameMovie.split(" ").join("").split(""); const getKey = (e: any) => { console.log("test") const key = e.key let count = 0 for (let i = 65; i <= 90; i++) { if (key.toUpperCase() == String.fromCharCode(i)) { count = 1 } else if (key.toLowerCase() == "backspace") { count = 10 } } if (count == 1) { setIndexArray(prev => { const newIndexArray = [...prev].sort((a,b) => a - b) setArrayMovie(prevMovie => { const newMovie = [...prevMovie] newMovie.splice(newIndexArray[idLetter], 1, key) return newMovie }) return newIndexArray }) setIdLetter(prev => prev + 1) } else if (count == 10) { if (idLetter >= 1) { setIdLetter(prev => prev - 1) setIndexArray(prev => { const newIndexArray = [...prev] setArrayMovie(prevMovie => { const newMovie = [...prevMovie] newMovie.splice(newIndexArray[idLetter - 1], 1, "") return newMovie }) return newIndexArray }) } } setKeyBoard(key) } useEffect(() => { const initIndexArray = [] const initArrayName = [...arrayNameMovie] for (let i = 3; i >= 0; i--) { const randomIndex = Math.floor(Math.random() * initArrayName.length) initArrayName.splice(randomIndex, 1, " ") initIndexArray.push(randomIndex) } setIndexArray(initIndexArray) setIdLetter(initIndexArray[0]) setArrayMovie(initArrayName) }, []) // 统一在useEffect中管理事件监听 useEffect(() => { document.addEventListener("keydown", getKey) // 卸载时自动清理监听 return () => { document.removeEventListener("keydown", getKey) } }, [idLetter]) // 回调中用到的动态状态加入依赖数组 return ( <div className="down__word"> {arrayMovie.map((letter: any, idx: number) => <LettersView key={idx} letters={letter} props={undefined} /> )} </div> ) }
额外注意事项
- 不要直接修改useState定义的状态:原代码中直接用
arrayMovie.splice、indexArray.push修改状态,属于非法的直接变更,会导致状态更新不可预期,应该通过状态更新函数的回调形式生成新的数组来修改。 - 所有副作用(事件监听、定时器、接口请求等)都应该放到useEffect中管理,避免重复执行和内存泄漏。
内容的提问来源于stack exchange,提问作者reszi.
相关产品推荐
相关产品推荐

