React中如何调用依赖更新后状态的回调函数?——解决AccessToken刷新后回调使用旧状态的问题
Fixing the Stale Access Token Issue in Your
useRequest Hook 我完全懂你遇到的这个坑——React的状态更新是异步的,所以当refresh里调用setAccessToken后立刻执行回调,回调里的request还是会用上一轮的旧accessToken,导致重试请求依然带着过期令牌失败。下面给你几个在自定义Hook场景下好用的解决办法:
方案1:直接把新AccessToken传给回调
既然我们在refresh里已经拿到了新鲜的accessToken,没必要等状态更新完成,直接把它作为参数传给回调函数,让request能直接用这个最新值。修改后的代码如下:
function useRequest() { const [accessToken, setAccessToken] = useState(42); const [refreshToken, setRefreshToken] = useState(7); // 给request加个可选参数,支持传入临时令牌 const request = async (url, tempToken) => { const tokenToUse = tempToken || accessToken; try { // 注意axios的配置项是小写的headers,不是Headers const res = await axios.get(url, { headers: { Auth: tokenToUse } }); return res; // 别忘了返回响应结果给调用方 } catch (error) { // 用可选链避免error.response不存在时报错 if (error.response?.status === 401) { // 调用refresh时传入回调,让它把新令牌传回来 await refresh((newToken) => request(url, newToken)); } else { // 把其他错误抛出去,方便上层处理 throw error; } } } const refresh = async (callback) => { const res = await axios.get("myserver.com", { params: { refreshToken }}); const newAccessToken = res.data.accessToken; setAccessToken(newAccessToken); callback(newAccessToken); // 直接把新令牌传给回调 } return request; }
这个方法简单直接,完全绕开了状态更新的异步延迟问题,拿到新令牌就立刻用它重试。
方案2:用useRef保存最新令牌
如果不想修改request的参数,可以用useRef来维护一个始终指向最新accessToken的引用——ref的更新是同步的,不会有状态更新的延迟。这样request里直接取ref的值,就永远不会拿到旧令牌了:
function useRequest() { const [accessToken, setAccessToken] = useState(42); const [refreshToken, setRefreshToken] = useState(7); // 用ref同步保存最新的accessToken const accessTokenRef = useRef(accessToken); // 当状态更新时,同步更新ref的值 useEffect(() => { accessTokenRef.current = accessToken; }, [accessToken]); const request = async (url) => { try { const res = await axios.get(url, { headers: { Auth: accessTokenRef.current } }); return res; } catch (error) { if (error.response?.status === 401) { await refresh(); // 刷新后ref已经是最新令牌了,直接重试 return request(url); } else { throw error; } } } const refresh = async () => { const res = await axios.get("myserver.com", { params: { refreshToken }}); setAccessToken(res.data.accessToken); // 这里不需要回调,ref会自动同步状态 } return request; }
这个方案适合希望保持request函数接口简洁的场景,用ref作为状态的“实时镜像”。
小提醒
你原来的代码里写的是Headers,但axios的配置项里应该是小写的headers,这个小细节可能会导致授权头无法正确发送,记得修正哦。
内容的提问来源于stack exchange,提问作者Grant Pitt
相关产品推荐
相关产品推荐

