关于React useEffect、清理函数与事件监听组合的疑问
React useEffect 事件监听疑问解答
用户问题背景
浏览器控制台日志:
page is rendered page is rendered---------------------------VM315 installHook.js:1861 App.js:65 click event listener is added(标记为NUM1) App.js:68 click event listener is removed(标记为NUM2) App.js:65 click event listener is added(标记为NUM3)
疑问:
- 我理解初始渲染时会添加click事件监听(NUM1),此时不会执行回调函数
calculateOffset,退出useEffect时移除该事件监听(NUM2),这部分理解是否正确? - 为何之后控制台又打印了一次事件监听添加(NUM3)?我想知道第二次useEffect是在哪个阶段触发并添加事件监听的。我认为不是因为
dimensions变化,因为第一次useEffect中calculateOffset未执行,dimensions仍为初始空对象,我的理解有误吗? - 另外,当useEffect绑定的事件监听未被触发时,该事件监听会一直等待用户触发吗?useEffect的清理函数何时会生效?
相关代码:
JavaScript 代码
import React from 'react' import { useState,useRef,useEffect } from 'react'; function App() { const [dimensions, setDimensions] = useState({}); const block = useRef(null); const calculateOffset = () => { console.log(block.current.getBoundingClientRect()); const {top,height,width} = block.current.getBoundingClientRect(); setDimensions({ top, height, width}); console.log("dimentions setted"); // console.log(top, height, windowInnerHeight, togglePoint); }; useEffect(() => { console.log("click event listener is added"); window.addEventListener("click", calculateOffset); return () => { console.log("click event listener is removed"); window.removeEventListener("click", calculateOffset); }; },[dimensions]); console.log("page is rendered"); return ( <div className='container'> <div className='block' ref={block} ></div> </div> ) } export default App
CSS 代码
*{ box-sizing: border-box; margin: 0; padding: 0; } .container{ height: 200vh; width: 100%; display: flex; align-items: center; justify-content: center; } .block{ width: 500px; height: 500px; background-color: lightblue; }
解答
一、初始渲染阶段的监听逻辑纠正
你的初始理解存在偏差:
- 初始渲染完成后,React会执行useEffect,添加click事件监听(对应NUM1)。此时不会立刻执行清理函数——useEffect的清理函数(return的匿名函数)并不是“退出useEffect时”触发,它的触发时机是组件下一次渲染前,或者组件卸载时。
- 你看到的NUM2(监听被移除),是因为组件发生了第二次渲染,React会先执行上一轮useEffect的清理函数,再执行新的useEffect(对应NUM3,重新添加监听)。
二、第二次useEffect触发(NUM3)的原因
核心原因是开发环境下的React Strict Mode:
- 在Strict Mode中,React会刻意让组件渲染、useEffect等逻辑执行两次,目的是检测不安全的副作用(比如未正确清理的事件监听、内存泄漏等)。
- 你的判断是对的:这次重新渲染和
dimensions无关——因为你还没点击页面,calculateOffset未执行,setDimensions没被调用,dimensions始终是初始空对象,依赖项没有变化。 - 控制台日志里的
VM315 installHook.js标记,也能佐证这是在开发环境的React DevTools运行环境中,Strict Mode默认开启。
三、事件监听的等待状态与清理函数生效时机
- 未触发的事件监听会一直绑定在目标元素(这里是window)上,等待用户触发对应事件,直到清理函数执行将其移除。
- 清理函数的生效时机分为三种:
- 组件卸载时:所有useEffect的清理函数都会被执行;
- useEffect依赖项变化时:组件重新渲染前,会先执行上一轮useEffect的清理函数,再执行新的useEffect;
- 开发环境Strict Mode下:第一次useEffect执行后会立即触发清理函数,随后再执行一次useEffect(这就是NUM2紧跟NUM1出现的原因)。
内容的提问来源于stack exchange,提问作者Yao Lu
相关产品推荐
相关产品推荐

