为什么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])
额外排查点
- 生产环境如果是HTTP协议,
secure标记的refreshTokenCookie会无法被携带,导致刷新请求失败,必须确保部署环境为HTTPS。 - 检查
useRefreshMutation的请求配置,确认生产环境下的baseURL、跨域设置是否正确。
内容的提问来源于stack exchange,提问作者DocSJ1980
相关产品推荐
相关产品推荐

