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

为什么useEffect在本地环境正常运行,生产环境却失效?

生产环境下React useEffect清理函数未执行导致刷新令牌逻辑失效

问题描述

我在app.jsx中实现了令牌刷新逻辑:当accessToken不存在时,通过带httpOnly和secure标记的refreshToken Cookie调用useRefreshToken mutation获取新的accessToken。本地开发环境中所有console.log输出都符合预期,但执行npm run build部署后,只有useEffect内的第一个日志输出,清理函数里的日志完全没执行,导致后续的令牌刷新逻辑无法触发。

相关代码

const [persist] = usePersist()
const token = useSelector(selectCurrentToken)
const effectRan = useRef(false)
const [trueSuccess, setTrueSuccess] = useState(false)

const [refresh, {
    isUninitialized,
    isLoading,
    isSuccess,
    isError,
    error
}] = useRefreshMutation()

useEffect(() => {
    console.log('verifying refresh token useffect running', token, persist)

    if (effectRan.current === true) {
        console.log('verifying refresh token effectRan.current')

        const verifyRefreshToken = async () => {
            console.log('verifying refresh token tryCatch running')
            try {
                await refresh()
                setTrueSuccess(true)
            }
            catch (err) {
                console.error(err)
            }
        }
     if (!token && persist) verifyRefreshToken()
     }

     return () => {
     console.log("EffectRan: ", effectRan.current, token, persist)
     effectRan.current = true
     console.log("EffectRan: ", effectRan.current)
     }
}, [])

问题原因

你的逻辑严重依赖React开发环境Strict Mode的特殊行为:开发环境下Strict Mode会刻意重复执行effect和对应的清理函数,所以清理函数会被触发,effectRan.current被设为true,后续逻辑能正常运行。但生产环境中Strict Mode不会做这种重复执行,而App作为根组件通常不会卸载,清理函数永远不会被调用,effectRan.current一直是false,导致内部的刷新逻辑完全无法触发。

解决方案

方案1:重构逻辑,移除对清理函数的依赖

把effectRan的标记逻辑直接放在effect内部,不再依赖清理函数触发:

const [persist] = usePersist()
const token = useSelector(selectCurrentToken)
const effectRan = useRef(false)
const [trueSuccess, setTrueSuccess] = useState(false)

const [refresh, {
    isUninitialized,
    isLoading,
    isSuccess,
    isError,
    error
}] = useRefreshMutation()

useEffect(() => {
    console.log('verifying refresh token useffect running', token, persist)

    // 首次执行时标记为已运行,直接返回
    if (!effectRan.current) {
        effectRan.current = true;
        return;
    }

    const verifyRefreshToken = async () => {
        console.log('verifying refresh token tryCatch running')
        try {
            await refresh()
            setTrueSuccess(true)
        }
        catch (err) {
            console.error(err)
        }
    }
    if (!token && persist) verifyRefreshToken()
}, [token, persist, refresh])

注意:必须把token、persist、refresh加入依赖数组,避免闭包导致的 stale 值问题。

方案2:简化逻辑,直接在首次渲染时检查令牌

如果你的需求只是在组件首次渲染时检查并刷新令牌,可以完全去掉effectRan标记,直接执行逻辑:

const [persist] = usePersist()
const token = useSelector(selectCurrentToken)
const [trueSuccess, setTrueSuccess] = useState(false)

const [refresh, {
    isUninitialized,
    isLoading,
    isSuccess,
    isError,
    error
}] = useRefreshMutation()

useEffect(() => {
    console.log('verifying refresh token useffect running', token, persist)

    const verifyRefreshToken = async () => {
        console.log('verifying refresh token tryCatch running')
        try {
            await refresh()
            setTrueSuccess(true)
        }
        catch (err) {
            console.error(err)
        }
    }
    // 首次渲染时直接检查并执行刷新
    if (!token && persist) verifyRefreshToken()
}, [token, persist, refresh])

额外排查点

  1. 生产环境如果是HTTP协议,secure标记的refreshToken Cookie会无法被携带,导致刷新请求失败,必须确保部署环境为HTTPS。
  2. 检查useRefreshMutation的请求配置,确认生产环境下的baseURL、跨域设置是否正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 13:25:19