自定义Hook内更新state后订阅组件未同步状态问题排查
问题分析与解决方案
核心问题
你的代码存在两个关键问题:
- setState异步特性:调用
setTokens后,React不会立刻更新tokens状态,后续立即执行accountsAndAuthCheck时,读取的仍是旧状态值。 - 闭包陷阱:
useTokens中的setTokens依赖了当前渲染周期的tokens值,若组件多次渲染,可能捕获旧状态导致更新逻辑出错。
修复步骤
1. 修复useTokens的闭包问题(必须做)
修改useTokens.tsx,使用函数式更新获取最新状态,避免闭包捕获旧值:
import { useState } from "react"; export default function useTokens() { const [tokens, setTokensState] = useState({ apiToken: localStorage.getItem("apiToken"), refreshToken: localStorage.getItem("refreshToken"), }); const setTokens = (tokenParams: { apiToken: string | null; refreshToken: string | null; }) => { // 用函数式更新获取最新状态prevTokens setTokensState(prevTokens => { const newTokens = { ...prevTokens, ...tokenParams }; // 同步更新localStorage newTokens.apiToken ? localStorage.setItem("apiToken", newTokens.apiToken) : localStorage.removeItem("apiToken"); newTokens.refreshToken ? localStorage.setItem("refreshToken", newTokens.refreshToken) : localStorage.removeItem("refreshToken"); return newTokens; }); }; return { tokens, setTokens }; };
2. 解决setTokens后立即使用旧状态的问题
提供两种可行方案:
方案一:直接传递新token给检查函数
无需依赖组件状态,直接把新获取的token传入accountsAndAuthCheck:
const { tokens, setTokens } = useTokens(); ... refreshToken(tokens.refreshToken!).then( ({ isSuccessful, data, status }) => { if (isSuccessful) { const newApiToken = data.access_token; const newRefreshToken = data.refresh_token; setTokens({ apiToken: newApiToken, refreshToken: newRefreshToken }); // 直接传新token给检查函数 accountsAndAuthCheck(newApiToken); } else { console.log("refresh", data, status); setTokens({ apiToken: null, refreshToken: null }); navigate("/link", { replace: true }); } } ); // 修改函数,接收token参数 const accountsAndAuthCheck = (apiToken: string) => { getAccounts(apiToken).then(({ isSuccessful, data, status }) => { if (isSuccessful) { setIsAuthorised(1); const _accountId = data.accounts[0].id; setAccountId(_accountId); } else { if (status == 403) { setIsAuthorised(-1); } } }); };
方案二:用useEffect监听状态变化
通过监听tokens的变化,在状态更新后自动执行检查逻辑:
import { useEffect, useState } from "react"; const { tokens, setTokens } = useTokens(); // 标记是否需要执行账户检查 const [triggerAccountCheck, setTriggerAccountCheck] = useState(false); ... refreshToken(tokens.refreshToken!).then( ({ isSuccessful, data, status }) => { if (isSuccessful) { setTokens({ apiToken: data.access_token, refreshToken: data.refresh_token, }); // 触发检查 setTriggerAccountCheck(true); } else { console.log("refresh", data, status); setTokens({ apiToken: null, refreshToken: null }); navigate("/link", { replace: true }); } } ); // 监听状态变化,执行检查 useEffect(() => { if (triggerAccountCheck && tokens.apiToken) { accountsAndAuthCheck(); // 重置标记,避免重复执行 setTriggerAccountCheck(false); } }, [tokens.apiToken, triggerAccountCheck]); // 原检查函数保持不变 const accountsAndAuthCheck = () => { getAccounts(tokens.apiToken!).then(({ isSuccessful, data, status }) => { if (isSuccessful) { setIsAuthorised(1); const _accountId = data.accounts[0].id; setAccountId(_accountId); } else { if (status == 403) { setIsAuthorised(-1); } } }); };
为什么await/then没用?
setState本身不返回Promise,即使在setTokens后添加await或.then(),也无法等待状态更新完成,因为React的状态更新是异步批量处理的。
内容的提问来源于stack exchange,提问作者Joosh
相关产品推荐
相关产品推荐

