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

自定义Hook内更新state后订阅组件未同步状态问题排查

问题分析与解决方案

核心问题

你的代码存在两个关键问题:

  1. setState异步特性:调用setTokens后,React不会立刻更新tokens状态,后续立即执行accountsAndAuthCheck时,读取的仍是旧状态值。
  2. 闭包陷阱: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 01:45:56