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

React组件卸载时useEffect清理函数中state非最新,如何获取最新值?

问题原因

你遇到的是典型的闭包陷阱:你当前写的useEffect依赖项是空数组,所以这个effect仅会在组件首次挂载时执行1次,它返回的清理函数捕获的是首次挂载时的上下文,里面的state都是初始值,后续state更新不会同步到这个闭包里,所以拿到的永远是初始值。


解决方法

方法1:将需要用到的state加入useEffect依赖项

这是最直接的解法,把清理函数里用到的state加到依赖数组里,每次state更新时effect都会重新执行,清理函数也会同步捕获最新的state值。
注意:这个方案下,清理函数不仅会在组件卸载时执行,还会在每次依赖更新前执行。如果你只需要在卸载时执行业务逻辑,可以加个标记判断:

const [num, setNum] = useState(4);
const isUnmounting = useRef(false);

// 专门标记是否进入卸载流程
useEffect(() => {
  return () => {
    isUnmounting.current = true;
  };
}, []);

useEffect(() => {
  return () => {
    if (isUnmounting.current) {
      // 只有组件真正卸载时才执行这里的逻辑
      console.log("卸载时的最新num值:", num);
      // 在这里发起你的HTTP请求即可
    }
  };
}, [num]); // 把需要用到的state加到依赖里

方法2:用useRef同步最新state

如果你不想让effect依赖state、也不想频繁触发effect的执行,可以用useRef来存储最新的state值,每次state更新时同步给ref,清理函数直接读取ref的current值即可——ref的引用不会变,current属性永远存储的是最新值:

const [num, setNum] = useState(4);
const numRef = useRef(num);

// 实时同步state到ref
useEffect(() => {
  numRef.current = num;
}, [num]);

useEffect(() => {
  return () => {
    // 直接读ref就能拿到最新值
    console.log("卸载时的最新num值:", numRef.current);
    // 在这里发起你的HTTP请求即可
  };
}, []); // 原effect的依赖还是空数组,仅挂载时执行一次

这个方案的好处是侵入性低,不需要修改原有空依赖effect的执行逻辑,清理函数只会在卸载时执行一次。

方法3:使用React 18的useEffectEvent(推荐)

React 18新增了useEffectEventAPI(目前已稳定),专门用来解决这类问题:你可以把需要读取最新state、但不需要作为effect依赖的逻辑放到useEffectEvent包裹的函数里,这个函数不需要加到effect的依赖中,内部永远能拿到最新的state和props:

import { useEffect, useEffectEvent, useState } from 'react';

const [num, setNum] = useState(4);

// 用useEffectEvent包裹卸载逻辑
const handleUnmount = useEffectEvent(() => {
  // 这里永远能拿到最新的num,不需要加到依赖里
  console.log("卸载时的最新num值:", num);
  // 在这里发起你的HTTP请求即可
});

useEffect(() => {
  return () => {
    handleUnmount();
  };
}, []); // 依赖还是空数组,只会挂载执行一次

额外兼容场景:页面关闭/刷新

如果你的需求是用户离开页面(包括关闭标签页、刷新页面)时发请求,仅靠React组件卸载的清理函数是不够的,这种场景下页面会直接终止进程,React的清理逻辑可能来不及执行,需要额外监听beforeunload事件,并且用navigator.sendBeacon来发请求,浏览器会保证这个请求在页面关闭前完成:

useEffect(() => {
  const handleBeforeUnload = () => {
    // 用sendBeacon发请求不会被浏览器中断
    navigator.sendBeacon('你的接口地址', JSON.stringify({ num: numRef.current }));
  };
  window.addEventListener('beforeunload', handleBeforeUnload);
  
  return () => {
    window.removeEventListener('beforeunload', handleBeforeUnload);
    // 组件正常卸载时的逻辑
    handleUnmount();
  };
}, []);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 14:12:03