You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于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)

疑问:

  1. 我理解初始渲染时会添加click事件监听(NUM1),此时不会执行回调函数calculateOffset,退出useEffect时移除该事件监听(NUM2),这部分理解是否正确?
  2. 为何之后控制台又打印了一次事件监听添加(NUM3)?我想知道第二次useEffect是在哪个阶段触发并添加事件监听的。我认为不是因为dimensions变化,因为第一次useEffect中calculateOffset未执行,dimensions仍为初始空对象,我的理解有误吗?
  3. 另外,当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)上,等待用户触发对应事件,直到清理函数执行将其移除。
  • 清理函数的生效时机分为三种:
    1. 组件卸载时:所有useEffect的清理函数都会被执行;
    2. useEffect依赖项变化时:组件重新渲染前,会先执行上一轮useEffect的清理函数,再执行新的useEffect;
    3. 开发环境Strict Mode下:第一次useEffect执行后会立即触发清理函数,随后再执行一次useEffect(这就是NUM2紧跟NUM1出现的原因)。

内容的提问来源于stack exchange,提问作者Yao Lu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 23:16:12