React中移除useEffect清理函数后keypress事件监听器为何执行两次?
回车键触发两次请求的原因分析
问题场景
编码时遇到一个问题:按回车键发送请求,发现keypress事件监听器执行了两次。后来在组件卸载时添加了事件监听器的移除逻辑,问题就解决了,但不清楚背后的原因。相关代码如下:
const inputRef = useRef(null); const fileRef = useRef(null); const [files, setFile] = useState([]); const sendMessage = useCallback(() => { if (inputRef.current.value === '' && files.length === 0) { return; } console.log('f', inputRef.current.value, files); if (files.length === 0) { AxiosClient.post('/general-msg', { orderId, recipient, content: inputRef.current.value }); inputRef.current.value = ''; } else { const messagePackage = new FormData(); messagePackage.append('orderId', orderId); messagePackage.append('recipient', recipient); messagePackage.append('content', inputRef.current.value); files.map((file) => { return messagePackage.append('file', file); }); AxiosClient.post('/message', messagePackage, { headers: { 'Content-Type': 'multipart/form-data' } }); inputRef.current.value = ''; setFile([]); } }, [files, inputRef, orderId, recipient]); const registerSendingTrigger = useCallback( (e) => { console.log('heelo'); if (e.code === 'Enter') { console.log('sending...'); // sending logs twice unless I write below sendMessage(); } }, [sendMessage] ); useEffect(() => { if (inputRef.current) { inputRef.current.removeEventListener('keypress', registerSendingTrigger); inputRef.current.addEventListener('keypress', registerSendingTrigger); } return () => inputRef.current.removeEventListener('keypress', registerSendingTrigger); // this part!!!!!!!! // why does send request gets twice when I delete return state?? }, [inputRef, registerSendingTrigger]); useEffect(() => { if (fileRef) { fileRef.current.addEventListener('change', (e) => { if (!e.target.files[0]) { return; } setFile((current) => { return [...current, e.target.files[0]]; }); }); } }, []);
原因解析
1. 重复绑定事件监听器的根源
你的useEffect依赖了registerSendingTrigger,而registerSendingTrigger又依赖sendMessage。当files状态更新时(比如添加文件后),sendMessage会因为依赖项变化重新创建,进而导致registerSendingTrigger也生成新的函数引用。
此时如果没有清理函数:
- 每次
useEffect重新执行时,虽然你调用了removeEventListener,但传入的是新的registerSendingTrigger引用,无法移除之前绑定的旧版本监听器(因为removeEventListener要求必须和addEventListener使用完全相同的函数引用才能生效)。 - 旧的监听器依然绑定在输入框元素上,新的监听器又被添加进去,最终输入框上会存在多个
keypress监听器实例。
2. 清理函数的作用
useEffect的返回函数是清理函数,它会在两个时机执行:
- 组件卸载时;
- 当前
useEffect因为依赖项变化重新执行前。
添加清理函数后,每次重新绑定新的监听器前,都会用当前的registerSendingTrigger引用移除旧的监听器(组件卸载时也会彻底移除),确保输入框上始终只有一个有效的keypress监听器,自然不会出现触发两次的情况。
内容的提问来源于stack exchange,提问作者Justin Seo
相关产品推荐
相关产品推荐

