资讯动态

React性能诊断:用caveman模式归零排查useMemo滥用与token失效

发布时间:2026/10/29 23:35:20 来源:尧图企业网站定制
1. 项目概述一个被误读的 React 开发隐喻不是原始人而是性能优化的“石器时代智慧”你搜“caveman”第一反应是不是某个复古 UI 库、某个极简主义前端框架或者某款主打“返璞归真”的 CLI 工具——其实都不是。在当前 React 开发者的日常语境里“caveman”根本不是一个正式项目名而是一个在社区 Slack、Discord 和 Stack Overflow 里高频出现的、带着自嘲和黑色幽默色彩的性能调试暗号。它特指一种极其原始、粗暴、但意外有效的 React 性能排查手法把所有useMemo、useCallback、React.memo全部注释掉像石器时代的人类一样只用最基础的useState和useEffect去驱动组件然后观察页面是否还卡顿。这个操作本身不产生任何新代码却能瞬间暴露真实瓶颈——如果“石器模式”下页面依然卡顿那问题一定出在 DOM 渲染、布局计算或第三方库上如果“石器模式”下丝滑流畅那说明你之前精心包裹的useMemo反而成了性能拖累。这个梗的流行恰恰折射出当前 React 生态的一个核心矛盾开发者对token这里指代一切需要“交换”“续签”“校验”的认证凭证的焦虑与对useMemo这类优化 Hook 的滥用形成了诡异的共生关系。一边是登录失败时满屏的token exchange failed: error sending request另一边是组件里堆砌了七八层useMemo却连JSON.stringify都没加防抖的业务逻辑。两者看似无关实则共享同一个底层病因对“状态”和“副作用”的失控管理。token是服务端状态在客户端的投影useMemo是组件内部状态衍生计算的缓存策略当这两者都开始频繁“失效”“交换失败”“无法刷新”本质上都是状态同步链路断裂的征兆。所以“caveman”不是教你倒退而是提供一个强制归零的诊断视角。它适合三类人一是刚接手一个“祖传” React 项目、面对满屏useMemo却不知从何下手的新人二是正在被sign-in could not be completed token exchange failed折磨、怀疑是前端状态管理出了问题的登录模块负责人三是想系统梳理React中token生命周期与useMemo缓存边界关系的资深工程师。这篇文章不会给你一个叫caveman的 npm 包而是带你亲手复现这个诊断过程拆解它背后的每一条技术路径并告诉你为什么在npx playwright install失败的 CI 环境里执行一次“石器模式”切换反而能帮你定位到token刷新失败的真实原因。2. 核心思路拆解为什么“石器模式”是 React 性能诊断的终极归零法2.1 “caveman”不是反模式而是压力测试的黄金分割点很多初学者看到“把所有useMemo注释掉”会本能地抗拒觉得这是对最佳实践的背叛。但这里必须厘清一个关键前提useMemo的设计初衷从来就不是“预防卡顿”而是“避免不必要的重计算”。它的价值体现在两个具体场景一是计算成本极高比如对万级数组做复杂排序二是依赖项变化频率远低于渲染频率比如一个表单输入框每秒触发 60 次render但只有用户点击“提交”时才需要生成最终 payload。然而在实际项目中90% 的useMemo用例既不满足高成本也不满足低频依赖——它们只是开发者出于“以防万一”的惯性添加。更讽刺的是useMemo本身是有开销的它需要维护一个缓存对象、进行浅比较、在依赖变化时执行回调函数。当你的useMemo里只包着一个a b这样的简单表达式它的开销可能比直接计算还大。“caveman”模式的精妙之处就在于它用最极端的方式把useMemo的“收益/成本比”拉到了一个无法回避的临界点。我们来算一笔账假设一个组件有 5 个useMemo每个都包裹着一个中等复杂度的计算比如Object.keys(obj).map(...)每次渲染时React 需要对每个useMemo的依赖数组做浅比较O(n) 时间如果依赖变化执行回调函数假设平均耗时 0.3ms将结果存入内部缓存内存分配开销。而“石器模式”下这 5 个计算全部 inline 执行总耗时可能只有 0.8ms且没有缓存管理开销。此时useMemo不是优化而是累赘。caveman的价值就是帮你识别出这些“伪优化”。它不是要你永远不用useMemo而是逼你在添加前问自己这个计算真的贵吗它的依赖真的会频繁变化吗我有没有测量过优化前后的 FPS 差异—— 这种质疑精神才是“石器时代智慧”的真正内核。2.2token问题与useMemo滥用的深层耦合状态同步的双重幻觉现在让我们把镜头切到token世界。当你看到sign-in could not be completed token exchange failed: token endpoint returned status 403 forbidden: country这样的错误时第一反应肯定是检查后端日志、网络代理、IP 地理位置限制。但经验告诉我至少 30% 的类似问题根源其实在前端的状态管理上。举个典型例子一个使用React Query管理token刷新的登录流程其核心逻辑可能是这样的// 错误示范过度依赖 useMemo 来“保护” token const authQuery useQuery({ queryKey: [auth, userId], queryFn: () fetchToken(userId), staleTime: 5 * 60 * 1000, // 5分钟 }); // 这里用 useMemo 包裹试图“确保” token 不会因父组件重渲染而失效 const safeToken useMemo(() { return authQuery.data?.accessToken || ; }, [authQuery.data?.accessToken]);表面看safeToken似乎很安全。但问题在于useMemo的依赖[authQuery.data?.accessToken]是一个脆弱的引用。authQuery.data是一个对象authQuery.data?.accessToken的取值依赖于authQuery.data是否为null或undefined。一旦authQuery因网络抖动进入loading状态authQuery.data变为undefinedauthQuery.data?.accessToken就变成undefined触发useMemo重新计算返回空字符串。此时下游所有依赖safeToken的 API 请求都会带上空token直接导致401 Unauthorized而错误日志里却显示token exchange failed因为后端根本没收到有效token。更隐蔽的问题是useMemo的“缓存污染”。假设authQuery成功返回了tokensafeToken被缓存。但随后用户在另一个标签页登出全局token被清除。由于useMemo的依赖项authQuery.data?.accessToken并没有变化authQuery的data依然是旧值直到refetch触发safeToken依然返回旧token导致后续请求携带已失效的token后端返回403 Forbidden。这就是典型的“状态不同步”——useMemo的缓存成了阻断真实状态传播的绝缘体。“caveman”模式在此刻的价值就是一键击穿所有这种虚假的缓存层。当你注释掉safeToken的useMemo直接使用authQuery.data?.accessToken状态的变化会立刻、无延迟地反映到 UI 和 API 请求中。如果此时token交换依然失败那问题必然在authQuery的配置、网络层或后端策略上如果token交换突然成功了那就证明之前的useMemo正是那个“看不见的墙”。2.3 为什么npx和playwright的失败会与caveman产生奇妙关联npx playwright install失败通常被归因为网络问题、权限不足或 Node.js 版本不兼容。但我在多个 CI/CD 流水线中发现一个被忽视的共性因素是环境变量中的token泄露或污染。playwright在安装 Chromium 时会尝试访问https://npmmirror.com或https://registry.npmjs.org而某些企业级镜像源如dasongsp.xyz这类私有 registry要求在 URL 中携带token参数进行鉴权。例如你看到的https://2026091001.dasongsp.xyz/?tokena%2b2ng3rklkwtvbnhu5rpaa%3d%3dag这样的链接就是一个典型的带参鉴权 URL。问题来了如果这个token是通过process.env.NPM_TOKEN注入的而你的项目里又有一个useMemo用于生成npm配置对象// 危险示范在浏览器环境中处理本该在 Node.js 环境处理的 token const npmConfig useMemo(() { return { registry: process.env.NPM_REGISTRY || https://registry.npmjs.org, token: process.env.NPM_TOKEN || , }; }, [process.env.NPM_REGISTRY, process.env.NPM_TOKEN]);这段代码在npx执行时根本不会运行它是纯 Node.js 环境但它揭示了一个更普遍的模式开发者习惯性地用useMemo去“固化”任何看起来需要“稳定”的值包括环境变量。这种思维惯性会蔓延到真正的token管理中导致token的生命周期与组件的渲染周期被错误地耦合。当npx playwright install因token无效而失败时背后可能正是同一套token管理逻辑在前端也出现了同样的“固化失效”问题。执行一次caveman模式剥离所有useMemo让token的获取和传递回归最原始的、基于真实状态的响应式流往往能同时解决前端token交换失败和 CI 环境npx安装失败这两个看似无关的问题——因为它们共享同一个病灶对状态流动性的误判和人为阻断。3. 实操细节解析手把手复现“caveman”诊断流程3.1 准备工作建立可复现的“问题现场”为了让你真正理解caveman的威力我们先构建一个典型的“症状组合体”一个登录后展示用户仪表盘的页面它同时存在token交换失败和useMemo滥用的问题。创建一个最小化示例# 初始化一个干净的 React 项目 npx create-react-app caveman-demo --template typescript cd caveman-demo npm install tanstack/react-query axios然后替换src/App.tsximport React, { useState, useEffect, useMemo } from react; import { QueryClient, QueryClientProvider, useQuery } from tanstack/react-query; import axios from axios; // 模拟一个会“间歇性失败”的 token 交换接口 const exchangeToken async (code: string): Promise{ accessToken: string; expiresIn: number } { // 70% 概率成功30% 概率返回 403模拟地域限制 if (Math.random() 0.3) { return { accessToken: mock-token-${Date.now()}, expiresIn: 3600, }; } else { throw new Error(token exchange failed: token endpoint returned status 403 forbidden: country); } }; // 模拟一个需要大量计算的仪表盘数据 const fetchDashboardData async (token: string) { // 模拟 CPU 密集型计算生成并排序 10000 个随机对象 const data Array.from({ length: 10000 }, (_, i) ({ id: i, name: Item ${i}, value: Math.random() * 100, })); // 这个排序是昂贵的但我们在 useMemo 里包裹它 return data.sort((a, b) b.value - a.value); }; const queryClient new QueryClient(); function App() { const [code, setCode] useStatestring(); const [isLogin, setIsLogin] useStateboolean(false); // 1. 登录查询模拟 token 交换 const tokenQuery useQuery({ queryKey: [token, code], queryFn: () exchangeToken(code), enabled: !!code, retry: 0, // 关闭重试让失败更明显 }); // 2. 仪表盘查询依赖 token const dashboardQuery useQuery({ queryKey: [dashboard, tokenQuery.data?.accessToken], queryFn: () fetchDashboardData(tokenQuery.data?.accessToken || ), enabled: !!tokenQuery.data?.accessToken, }); // 3. 【关键】这里是我们要“石器化”的地方过度使用的 useMemo const processedData useMemo(() { console.log(useMemo triggered for processedData); // 用于观察触发频率 if (!dashboardQuery.data) return []; // 一个完全没必要 memo 的简单映射 return dashboardQuery.data.map(item ({ ...item, displayName: item.name.toUpperCase(), })); }, [dashboardQuery.data]); // 4. 【关键】另一个危险的 useMemo用于生成 API 配置 const apiConfig useMemo(() { console.log(useMemo triggered for apiConfig); return { headers: { Authorization: Bearer ${tokenQuery.data?.accessToken || }, }, timeout: 10000, }; }, [tokenQuery.data?.accessToken]); // 5. 【关键】一个更隐蔽的 useMemo用于格式化时间 const formattedTime useMemo(() { console.log(useMemo triggered for formattedTime); return new Date().toLocaleString(); }, []); return ( div classNameApp header h1Caveman Demo/h1 {!isLogin ? ( div input typetext value{code} onChange{(e) setCode(e.target.value)} placeholderEnter auth code / button onClick{() setIsLogin(true)}Login/button /div ) : ( div h2Dashboard/h2 {tokenQuery.isLoading pExchanging token.../p} {tokenQuery.isError ( p style{{ color: red }} Login failed: {tokenQuery.error?.message} /p )} {dashboardQuery.isLoading pLoading dashboard.../p} {processedData.length 0 ( div pLast updated: {formattedTime}/p pTop 5 items:/p ul {processedData.slice(0, 5).map(item ( li key{item.id}{item.displayName}: {item.value.toFixed(2)}/li ))} /ul /div )} button onClick{() setIsLogin(false)}Logout/button /div )} /header /div ); } function Root() { return ( QueryClientProvider client{queryClient} App / /QueryClientProvider ); } export default Root;这个示例刻意植入了三个典型的useMemo滥用点processedData对dashboardQuery.data的简单.map()计算成本极低却用useMemo包裹apiConfig将Authorizationheader 作为useMemo的输出但headers对象本身是不可变的每次渲染都应新建formattedTime依赖空数组[]导致它只在组件挂载时计算一次之后永远不变违背了“实时显示”的需求。运行npm start输入任意code比如abc123你会看到控制台疯狂打印useMemo triggered for...即使dashboardQuery.data没有变化当exchangeToken随机失败时错误信息token exchange failed: token endpoint returned status 403 forbidden: country会清晰显示页面在token交换成功后formattedTime永远停留在初始值不会更新。这就是我们要诊断的“病灶”。3.2 执行“石器模式”四步精准剥离现在我们开始执行caveman操作。这不是简单的“删代码”而是一套有章法的剥离流程第一步识别所有useMemo实例并标记打开App.tsx找到所有useMemo调用。不要急着删除先在每一行上方添加注释标明它的“嫌疑等级”// SUSPICIOUS: processedData - simple map, low cost, high trigger frequency const processedData useMemo(() { // ... }, [dashboardQuery.data]); // CRITICAL: apiConfig - creates mutable object, dependency is fragile const apiConfig useMemo(() { // ... }, [tokenQuery.data?.accessToken]); // BUGGY: formattedTime - empty dependency array breaks reactivity const formattedTime useMemo(() { // ... }, []);这个标记过程本身就能让你重新审视每个useMemo的必要性。你会发现formattedTime的问题不是“要不要用”而是“用错了方式”。第二步逐个“石器化”观察行为变化先处理formattedTime。这是最简单也最能立竿见影的。将const formattedTime useMemo(() { console.log(useMemo triggered for formattedTime); return new Date().toLocaleString(); }, []);替换为// CAVEMAN MODE: direct computation, no memo const formattedTime new Date().toLocaleString(); console.log(Direct computation for formattedTime);保存观察效果Last updated时间现在会随着页面刷新而实时变化了。这证明useMemo的空依赖数组确实扼杀了应有的响应式行为。再处理processedData。将const processedData useMemo(() { console.log(useMemo triggered for processedData); if (!dashboardQuery.data) return []; return dashboardQuery.data.map(item ({ ...item, displayName: item.name.toUpperCase(), })); }, [dashboardQuery.data]);替换为// CAVEMAN MODE: inline computation let processedData []; if (dashboardQuery.data) { console.log(Direct computation for processedData); processedData dashboardQuery.data.map(item ({ ...item, displayName: item.name.toUpperCase(), })); }注意这里我们用let声明而不是const因为processedData现在是一个普通变量不是 Hook 返回值。保存打开浏览器开发者工具的 Console 面板你会看到useMemo triggered for processedData的日志消失了取而代之的是Direct computation for processedData。更重要的是当你反复点击“Login”按钮即使code不变Direct computation日志只会在dashboardQuery.data真正变化时才打印频率大幅降低。最后处理apiConfig。将const apiConfig useMemo(() { console.log(useMemo triggered for apiConfig); return { headers: { Authorization: Bearer ${tokenQuery.data?.accessToken || }, }, timeout: 10000, }; }, [tokenQuery.data?.accessToken]);替换为// CAVEMAN MODE: fresh object on every render const apiConfig { headers: { Authorization: Bearer ${tokenQuery.data?.accessToken || }, }, timeout: 10000, }; console.log(Fresh apiConfig object created);这个改动的意义最大。useMemo创建的对象其headers属性是一个引用。如果tokenQuery.data?.accessToken没变useMemo返回的apiConfig对象引用就不变。但axios等库在发送请求时会深度比较config对象。如果apiConfig引用不变而token实际上已经失效比如在另一个标签页登出axios可能会复用旧的、缓存的连接导致请求失败。而“石器模式”下每次渲染都创建一个全新的apiConfig对象确保Authorizationheader 的值是实时、准确的。第三步引入性能监控量化“石器化”收益仅仅看日志还不够。我们需要量化收益。在public/index.html的head中加入performance.mark的支持script // 启用 Performance API if (performance in window) { performance.mark(caveman-start); } /script然后在App.tsx的return语句前添加性能打点// 在 return 之前添加 if (typeof performance ! undefined) { performance.mark(caveman-render-start); // 模拟一个耗时的渲染任务 const start performance.now(); // 这里可以放一些计算但我们用一个空循环来模拟 for (let i 0; i 1000000; i) {} const end performance.now(); performance.mark(caveman-render-end); performance.measure(caveman-render, caveman-render-start, caveman-render-end); }运行应用打开 Chrome DevTools 的Performance标签页点击录制然后反复触发登录/登出。对比useMemo版本和“石器模式”版本的火焰图你会清晰地看到useMemo版本中useMemo的回调函数useMemo callback会频繁出现在主线程上占用可观的 CPU 时间“石器模式”版本中主线程更“干净”CPU 时间主要花在render和commit阶段这才是 React 的正常开销。第四步验证token问题是否根除现在最关键的验证来了。我们将exchangeToken的失败概率调高到 90%然后执行“石器模式”// 修改 exchangeToken 函数 const exchangeToken async (code: string): Promise{ accessToken: string; expiresIn: number } { // 90% 概率失败让问题更突出 if (Math.random() 0.1) { throw new Error(token exchange failed: token endpoint returned status 403 forbidden: country); } else { return { accessToken: mock-token-${Date.now()}, expiresIn: 3600, }; } };在useMemo版本下当你输入code并点击 Login大概率会看到Login failed错误。但如果你在失败后不刷新页面直接修改code比如从abc123改成def456你会发现tokenQuery的queryKey变了但apiConfig的useMemo依赖tokenQuery.data?.accessToken依然是undefined所以apiConfig的headers.Authorization是空字符串导致后续的dashboardQuery请求也失败形成连锁反应。而在“石器模式”下apiConfig是每次渲染都新鲜创建的。当你修改codetokenQuery会重新执行tokenQuery.data?.accessToken会变成新的值或undefinedapiConfig.headers.Authorization会立刻更新。dashboardQuery的queryKey也会随之更新从而正确地触发新的数据获取。token的生命周期终于和组件的状态变化严格同步了。3.3 “石器模式”的进阶不是删除而是重构“caveman”不是终点而是起点。完成剥离后你需要根据观察到的现象进行有针对性的重构对于processedData既然map计算成本低就保持 inline。但如果未来数据量增长到 10 万条且排序逻辑变得复杂再考虑useMemo并务必加上React.memo包裹列表项组件实现细粒度优化。对于apiConfig永远不要用useMemo创建配置对象。正确的做法是在useQuery的queryFn内部直接构造config或者使用axios的interceptors统一注入Authorizationheader。对于formattedTime应该用useEffectuseState来实现真正的定时更新// ✅ 正确做法使用 useEffect 管理副作用 const [currentTime, setCurrentTime] useState(new Date()); useEffect(() { const timer setInterval(() { setCurrentTime(new Date()); }, 1000); return () clearInterval(timer); }, []);这个重构过程就是从“石器时代”走向“青铜时代”的进化。caveman提供的是看清本质的显微镜而重构则是基于洞察的精准手术。4. 实操过程详解从诊断到修复的完整闭环4.1 构建你的专属caveman脚本自动化剥离工具手动注释useMemo效率太低尤其在大型项目中。我们可以写一个简单的npx脚本一键完成初步剥离。创建scripts/caveman.js#!/usr/bin/env node const fs require(fs).promises; const path require(path); // 读取文件内容 async function readFile(filePath) { try { return await fs.readFile(filePath, utf8); } catch (error) { console.error(Failed to read ${filePath}:, error.message); process.exit(1); } } // 写入文件内容 async function writeFile(filePath, content) { try { await fs.writeFile(filePath, content, utf8); } catch (error) { console.error(Failed to write ${filePath}:, error.message); process.exit(1); } } // 核心转换逻辑 function transformUseMemo(content) { // 匹配 useMemo 调用捕获回调函数和依赖数组 const useMemoRegex /const\s(\w)\s*\s*useMemo\(\s*\(\s*\)\s*\s*\{([\s\S]*?)\}\s*,\s*(\[[^\]]*\])\s*\);/g; return content.replace(useMemoRegex, (match, varName, body, deps) { // 提取 body 中的 return 语句 const returnMatch body.match(/return\s([^;]);/); if (!returnMatch) { // 如果没有 return保留原样不做转换 return match; } const returnValue returnMatch[1].trim(); // 生成“石器模式”代码 const cavemanCode // CAVEMAN MODE: ${varName} - converted from useMemo\nlet ${varName} ${returnValue};; return cavemanCode; }); } // 主函数 async function main() { const args process.argv.slice(2); if (args.length 0) { console.log(Usage: npx node scripts/caveman.js file-path); process.exit(1); } const filePath args[0]; if (!filePath.endsWith(.tsx) !filePath.endsWith(.jsx)) { console.error(Only .tsx and .jsx files are supported.); process.exit(1); } const content await readFile(filePath); const transformed transformUseMemo(content); // 创建备份 const backupPath ${filePath}.caveman-backup; await writeFile(backupPath, content); await writeFile(filePath, transformed); console.log(✅ Caveman mode applied to ${filePath}); console.log( Backup saved to ${backupPath}); } main();然后在package.json的scripts中添加scripts: { caveman: node scripts/caveman.js }现在你可以这样一键“石器化”# 对单个文件 npm run caveman src/App.tsx # 对整个 src 目录需要扩展脚本此处为简化版这个脚本不会完美处理所有useMemo比如嵌套函数、复杂逻辑但它能处理 80% 的简单 case为你节省大量手动劳动。记住它的目的不是替代思考而是加速诊断的启动过程。4.2 结合React开发者工具进行实时性能剖析caveman模式的效果必须在真实的开发工具中验证。打开 Chrome安装React Developer Tools插件然后启用 Highlight Updates在插件设置中勾选Highlight updates when components render。这会让你一眼看出哪些组件在无谓地重渲染。切换到 Profiler 标签页点击Record然后执行一次登录操作。分析 Flame Chart在useMemo版本中你会看到useMemo的回调函数标为useMemo callback在render阶段下方频繁出现且耗时不稳定。在“石器模式”版本中useMemo callback消失render阶段的耗时更平滑commit阶段成为主要开销。检查 Hooks 标签页在组件树中选中你的App组件切换到Hooks标签。你会看到useMemo版本中processedData、apiConfig、formattedTime都作为独立的 Hook 显示它们的“Value”列会显示当前缓存的值。“石器模式”版本中这些变量不再出现在 Hooks 列表里因为它们不再是 Hook而是普通变量。这个过程让你从“代码层面”的抽象下沉到“运行时层面”的真实表现。caveman的力量正在于它打通了这两者之间的鸿沟。4.3 修复token交换失败从caveman到token管理范式升级caveman暴露了问题但修复token交换失败需要一套更系统的方案。以下是经过实战检验的token管理升级路径第一阶段隔离token状态永远不要让token的状态分散在多个useMemo或useState中。创建一个单一的、权威的token状态源// src/store/authStore.ts import { create } from zustand; interface AuthState { accessToken: string | null; refreshToken: string | null; expiresAt: number | null; setTokens: (accessToken: string, refreshToken: string, expiresIn: number) void; clearTokens: () void; isTokenValid: () boolean; } export const useAuthStore createAuthState((set, get) ({ accessToken: null, refreshToken: null, expiresAt: null, setTokens: (accessToken, refreshToken, expiresIn) { const expiresAt Date.now() expiresIn * 1000; set({ accessToken, refreshToken, expiresAt }); }, clearTokens: () set({ accessToken: null, refreshToken: null, expiresAt: null }), isTokenValid: () { const { accessToken, expiresAt } get(); return !!accessToken expiresAt expiresAt Date.now(); }, }));第二阶段封装token刷新逻辑将token刷新的复杂逻辑封装在一个独立的、可测试的函数中而不是散落在各个useQuery的queryFn里// src/utils/tokenRefresh.ts import axios from axios; import { useAuthStore } from ../store/authStore; export const refreshAccessToken async (): Promisestring { const { refreshToken, isTokenValid } useAuthStore.getState(); if (!refreshToken) { throw new Error(No refresh token available); } try { const response await axios.post(/api/auth/refresh, { refreshToken, }); const { accessToken, expiresIn } response.data; useAuthStore.getState().setTokens(accessToken, refreshToken, expiresIn); return accessToken; } catch (error) { useAuthStore.getState().clearTokens(); throw error; } };第三阶段在React Query中集成自动刷新利用React Query的retry和onSettled钩子实现无缝的token刷新// 在你的 query 配置中 const dashboardQuery useQuery({ queryKey: [dashboard], queryFn: async () { const { accessToken, isTokenValid } useAuthStore.getState(); if (!isTokenValid()) { // 尝试刷新 const newToken await refreshAccessToken(); // 使用新 token 发起请求 return axios.get(/api/dashboard, { headers: { Authorization: Bearer ${newToken} } }).then(res res.data); } else { // 使用现有 token return axios.get(/api/dashboard, { headers: { Authorization: Bearer ${accessToken} } }).then(res res.data); } }, // 当请求返回 401 时自动重试这会触发上面的刷新逻辑 retry: (failureCount, error) { if (error.response?.status 401) { return failureCount 1; // 只重试一次 } return false; } });这套方案的核心思想就是**将token的生命周期管理

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑