前端请求状态管理提速:自研轻量工具解决loading、竞态与缓存

最近在做一个数据密度很高的前端项目时,我发现自己陷入了一种极不健康的重复劳动:每个页面都在写loading、error、data三个状态,再用一个useEffect去发请求,然后处理刷新、竞态、缓存、卸载时防止内存泄漏。这套模板写了几十遍之后,我把这一层逻辑抽了出来,做成一个内部代号叫rea的小工具。它不是什么第三代框架,也不是要取代现有的请求库,而是只做一件事:让业务组件彻底不需要关心“请求该什么时候发、返回结果怎么缓存、重复请求怎么合并、旧响应怎么丢弃”这些底层细节。如果你也在维护一个中后台系统,且被各种异步请求的状态管理折磨过,这篇内容应该能给你一个非常轻量、可落地的替代方案。

1. 这个工具解决的问题到底是什么

1.1 前端请求状态代码的三处“慢性病”

我先把最让人头疼的代码形态摆出来。大多数业务页面里,请求逻辑是长这样的:

const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { let canceled = false; setLoading(true); fetchUserList() .then(res => { if (!canceled) { setData(res); } }) .catch(err => { if (!canceled) { setError(err); } }) .finally(() => { if (!canceled) { setLoading(false); } }); return () => { canceled = true; }; }, [params]);

这段代码基本是标准模板,但写多了之后,你会看到三个明显的问题。

第一,样板代码的量是幻觉般的多。三行状态定义、一个useEffect、一个canceled标志、三段回调处理。这段代码大概 30 行,而它只覆盖了一个请求。当页面有六个请求时,这类代码就占据了一百八十行,且其中真正有业务含义的只有fetchUserList()这一行。剩下的全是疲劳劳动。

第二,竞态问题很容易在“看着没问题”的时候爆发。上面的canceled变量只能在组件卸载时阻止更新,它拦不住另一种更常见的场景:用户快速切换筛选条件,前一个请求比后一个请求晚 300 毫秒返回,页面数据被旧响应覆盖。我见过很多线上 bug 都属于这种类型,因为它的触发条件是时间差,不是逻辑错误。

第三,同一份资源的请求无法共享。假设列表页和详情页都需要currentUser信息,这两处组件会各自发一次请求。如果再有一个侧边栏也要用到,那就变成三次请求。这种重复请求浪费了带宽,也增加了后端的压力。而且每次刷新都需要手动setData,很容易做成分叉逻辑,久而久之状态就开始乱了。

1.2 为什么不直接用一个请求库,而是自己做一版

你可能想说,这类问题早就被解决过了,比如知名的swr、react-query或者ahooks的useRequest。确实这些库都很成熟,我在早期调研时也认真对比过。但我在这个项目里最终选择了自己实现一个很小的方案,核心原因有三点。

其一,项目里的大部分请求并不需要粗粒度的“服务端状态管理”。我们不需要复杂的缓存标签体系、不需要 e标签不够、不需要分页自动扩展这些能力。我们需要的只是一套规则:请求发出去后,把数据放到一个统一的地方,任何人需要时都能拿到同一份结果,并且能设置一个合理的过期时间。这类需求用一个很小的封装就足够覆盖。

其二,生态库虽然强大,但它的概念模型是围绕自己的核心抽象展开的。这在团队协作中会引入额外的学习成本。新同学入职后需要理解缓存 key 的层级结构、失效策略、重渲染时机,这一套东西对于只维护两个后台页面的团队来讲,收益是负的。我更倾向于让代码保持“普通函数 + 普通状态”就能看懂的程度。

其三,我需要一个能完全掌控生命周期和内存行为的方案。有些业务流程要求请求结果在特定操作后必须物理丢弃,而不是继续留在全局缓存里给下一次刷新用。这种定制需求在大而全的库里反而要写各种拦截逻辑,倒不如在自己这一亩三分地里直接控制来得干净。

所以rea的定位不是“替代 react-query”,而是一个更小、更贴近业务请求层的协调器。它解决的就是从“业务组件主动发起请求”到“请求结果自动同步到所有依赖方”之间的这段空白。

2. 核心设计思路与关键取舍

2.1 把请求描述成一个“资源对象”

rea的第一个核心概念是resource。所谓资源,就是一段配置对象,它描述了一个请求的完整行为:

import { defineResource } from 'rea'; const userListResource = defineResource({ key: 'user-list', fetcher: () => fetch('/api/users').then(res => res.json()), staleTime: 30_000, // 30秒内认为数据是新鲜的 maxAge: 5 * 60_000, // 5分钟后无论是否新鲜,都需要重新拉取 retry: 2, });

这里的每个字段都有明确含义。key是整个资源共享和缓存的全局唯一标识。也就是说,只要两个地方使用同一个资源对象,它们拿到的就是同一份数据、同一个请求状态。fetcher必须是一个返回 Promise 的函数,它负责真正发起网络请求。staleTime和maxAge是缓存策略的核心,这俩的差别我后面单独讲。retry是失败重试次数,默认 0。

把请求描述成配置对象,而不是在组件里直接调用函数,目的是为了让请求成为可复用、可观察、可替换的实体。你想想看,在传统写法里,请求是在组件内联发生的,请求函数和组件的生命周期完全耦合;而一旦抽出成资源对象,这个请求就变成了一个独立的单元,它可以被挂载、被复用、被预取,甚至被测试替换。

在rea内部,每个资源对应一个条目的状态机:

idle → loading → success | error

请求未触发时是idle,触发后变为loading,完成后进入success或error。任意时刻只有这几个状态,组件只需要关心状态机的分支,不需要自己维护布尔组合。这是我认为最划算的一个抽象。

2.2 请求去重与竞态取消:两个容易翻车的细节

刚开始实现时,我先把去重和竞态分开做了,后来发现它们其实是同一枚硬币的两面:都是为了“请求结果只能有一个胜利者”。

去重的实现逻辑很直接。rea内部维护一个inFlight的 Map,key 是资源的全局 key,value 是当前正在执行的 Promise。当发起请求时,先查这个 Map,如果已存在同样的 key 的挂起请求,就不再发起新请求,而是直接复用那个已经存在的 Promise。这就保证了即使页面里三个组件同时挂载并都调用了刷新,也只会有一份真实的网络请求发到后端。我在某个页面上实测过,从三份请求降到一份,网络时间从平均三百毫秒降到一百毫秒,提升非常可观。

竞态取消则是用了一个自增序列号。每次发起请求时,给这次请求分配一个递增的seq,请求返回后,检查这个seq是否仍然等于当前资源最新的seq。如果不相等,说明在这段时间内又发起了新的请求,那么旧请求的结果应该被直接丢弃。这个方案的优点是简单可靠,不依赖 AbortController 也能解决大部分业务竞态问题。需要注意的是,释放网络连接这件事仍然应该由AbortController来做,只是数据状态层面不需要因为它而额外引入复杂度。

let seq = 0; async function runFetch(entry) { const currentSeq = ++seq; // 分配本次请求的编号 try { const result = await entry.fetcher(); if (currentSeq !== seq) return; // 已经过期,丢弃 entry.data = result; entry.status = 'success'; } catch (err) { if (currentSeq !== seq) return; entry.error = err; entry.status = 'error'; } emitChange(entry); // 通知所有订阅者 }

这里有一个我开发时踩过的坑:我刚实现时只判断了成功分支的过期,漏掉了失败分支。结果是在快速切换参数时,旧请求如果失败了,还是会触发一次 error 状态更新,导致页面闪一下错误提示。解决方式就是成功和失败分支都要做同样的seq校验,一个都不能少。

2.3 缓存只做“短时快照”,不越界

rea的缓存策略是刻意保守的。它把数据保存在运行时内存里,而不是放进localStorage或sessionStorage。

为什么不做持久化?因为请求数据很可能包含用户隐私或临时凭证,放进持久化存储后,刷新页面虽然可以读取,但你不得不考虑清理时机、多标签页同步、版本过期这些额外问题。一个最小方案把这些复杂度全部挡在外面,换来的是实现和心智的双重简单。

staleTime的含义是:在这段时间内,任何组件来读取资源时,会直接拿到缓存数据,而不会触发新的请求。这个时间适合设置为“业务上能接受的最大滞后时间”,比如用户列表设置成 30 秒,意味着用户不在 30 秒内重复拉取也能看到相对新的数据,如果期间真的有大量更新,用户手动刷新一下也能拿到新数据。

maxAge的含义是硬上限。哪怕还在staleTime内,一旦超过maxAge,下次读取就必须触发一次真实请求。这样做的意义是避免数据无限期停留在内存中。举个例子,用户切到别的页面再切回来,距离第一次请求已经过了十分钟,如果只有staleTime没有maxAge,读到的可能是十分钟前的数据,而它早就应该被更新了。这两个参数的组合实际上构成了一种姑且称之为“短时快照”的缓存模型:数据只在他的短暂生命周期内被复用,过期即消失,不会成为长期状态依赖。

3. 实操上手:把 rea 接进一个真实页面

3.1 初始化工程与安装依赖

假设你正在用 Vite 搭建一个 React + TypeScript 项目,接入rea的流程非常简单。先把包装进来:

npm install rea

然后不用做任何全局配置。rea不需要 Provider、不需要初始化函数,因为你可以在模块级别直接定义资源对象,而这些对象本身就是仓库。这种设计让我们不需要在应用根部插入任何上下文组件,对于想逐步引入到老项目的团队来说,这一点是很大的便利。

3.2 定义一个用户资源,并封装一个 hooks

我把资源定义放进独立文件resources/user.ts,这样好维护:

import { defineResource } from 'rea'; export interface User { id: number; name: string; avatar: string; } export const userResource = defineResource({ key: 'current-user', fetcher: async (params?: {id: number}) => { const res = await fetch(`/api/user/${params?.id ?? 'me'}`); if (!res.ok) throw new Error('加载用户失败'); return res.json() as Promise<User>; }, staleTime: 60_000, });

接下来封装一个useUser的 hook,让组件可以直接消费这个资源:

import { useRea } from 'rea'; import { userResource, User } from '../resources/user'; export function useUser(id?: number) { const query = useRea(userResource, { params: { id }, deps: [id], // 仅当 id 变化时,才重新拉取 }); return { user: query.data as User | null, loading: query.loading, error: query.error, refresh: query.refresh, }; }

初看这段代码,你可能觉着useRea做的事情很像一次useEffect+useState的封装。但关键区别在于:userResource是模块级单例,它背后的数据仓库也是全局唯一的。因此,无论哪个组件调用useUser,只要 key 相同,它们读取到的就是同一份data和同一个loading。这就是资源共享的本质。

3.3 三种消费姿势:默认加载、联动刷新、手动操作后失效

我在这里整理三种最常见的使用场景,基本可以覆盖中后台项目里八成以上的需求。

场景一:页面挂载后自动请求数据。这个最普通,但你不需要写任何useEffect:

function Profile() { const { user, loading, error } = useUser(); if (loading) return <Skeleton />; if (error) return <ErrorTip message={error.message} />; return <div>{user?.name}</div>; }

场景二:参数变化时自动重新请求。比如用户选择一个角色后,角色 ID 成为请求参数:

function RoleMemberList({ roleId }: { roleId: number }) { const { users, loading, refresh } = useUserListByRole(roleId); // useUserListByRole 内部会把 roleId 放进 params 和 deps return <Table loading={loading} dataSource={users} onRefresh={refresh} />; }

当roleId变化时,useRea会自动检测到依赖项变化,触发新一轮请求。这一步替代了原本手写的“useEffect 依赖[roleId]”逻辑,同时也自动保证了老请求不会覆盖新结果。

场景三:提交一个新增操作后,手动失效缓存并刷新。这个我认为是它最提升体验的地方。以前你需要手写一个refresh函数,把setData暴露出来;现在直接调用refresh即可:

const { refresh } = useUserList(); const handleCreate = async () => { await api.createUser(input); refresh(); // 触发重新拉取,所有依赖此资源的组件都会同步更新 };

即便有十个组件同时使用了同一个资源,refresh()一次,也会通知到所有订阅者,它们会同时进入loading状态并拿到新数据。这种响应式扩散能力是用手写状态很难实现的,我觉得它是整套方案里最有代入感的一个特性。

4. 踩过的坑与排查实录

4.1 无限请求:依赖项里的对象引用陷阱

我遇到的最严重的 bug,是接入useRea后页面出现无限循环请求。排查到最后发现原因出在deps数组写了一个对象引用:

useRea(resource, { params: { keyword }, deps: [keyword], });

这看起来没问题,但随后我改成了这样:

useRea(resource, { params: { filters }, // filters 是对象 deps: [filters], // 每次 render 都是新的引用 });

因为filters在父组件里是通过重组创建的新对象,导致rea认为依赖每次都在变化,于是不断触发请求。这其实是 React hook 依赖比对的老问题暴露在了自己的封装上。解决方式有两种:一是传基础类型作为资源 key 的一部分,二是使用稳定引用(比如useMemo或useRef)。我建议优先调整依赖结构,保证deps里只放原始值。

4.2 刷新时数据短暂消失的问题

第一次体验rea时,我发现每次手动刷新都会让页面先回到loading状态,之前展示的数据列表瞬间消失,然后整个表格变成骨架屏,最后再重新渲染。视觉上很割裂,尤其是用户只点了列表上一个刷新按钮,看到全屏闪烁会非常困惑。

为了解决这个问题,我给useRea增加了keepPreviousData参数。当设置为true时,请求刷新期间不会清空已有数据,只是把loading置为 true,并用旧的data继续渲染。这种做法在数据密集型页面里几乎成了标配,它让刷新动作变得无感,用户体验好了很多。

4.3 组件卸载后请求返回引发告警

在最初的原型版本里,我在请求完成后直接调用emitChange(entry),而并没有检查资源是否还有活跃订阅者。这导致一个问题:某些场景下,组件已经卸载,但请求的回调仍然触发了状态更新,虽然 React 18 之后这个不算致命错误,但控制台会输出告警,而且无谓的重渲染也应避免。

排查后的修复方案是:每个资源在内部维护一个订阅者 Set,emitChange前检查订阅者数量,如果没有订阅者,就不触发通知。同时,资源对象本身仍然保存着最新状态,后续有新的订阅者到来时,可以立刻读取到上次请求的结果。这个改动保证了内存使用和数据一致性之间的平衡。

4.4 一份快速问题排查表

我把实际项目中几个人最容易踩的问题整理成表格,方便你对照定位:

现象可能原因解法
页面无限请求、网络面板请求堆积deps中包含对象/数组等易变引用改为基础类型、稳定化引用或直接省略 deps
快速筛选时数据偶尔被旧值覆盖竞态取消逻辑只覆盖了成功分支或完全未处理为每次请求分配 seq,成功与失败分支都做过期校验
刷新时页面出现闪空默认行为是清空旧数据再加载给useRea加keepPreviousData: true
多个页面各发各的请求、后端重复请求量很大未使用同一个资源实例,或资源互相不共享确保使用模块级单例资源,key 保持全局唯一
切回页面时数据太旧、未自动更新staleTime设置过长且没有maxAge兜底引入maxAge作为硬过期时间,或在后台静默刷新
某操作后所有页面数据不同步只刷新了组件局部状态,没有调用资源级 refresh手动操作后统一调用资源的refresh方法
卸载组件后控制台出现 setState 告警没有判断订阅者是否还存在升级到带订阅者检查的版本,确保卸载后不派发更新

这张表不是万能药,但它覆盖了我在这类场景里遇到的大部分异常。排查时先看网络面板里的请求次数,再检查依赖项和控制台告警,基本就能锁定问题。


最后再分享一个我自己很受益的小习惯:像rea这种工具,不要期望它第一天就做成完美框架,我的建议是从一个真实项目里的一个高频场景出发,先抽取最小可用的 API,然后让同事试用一星期,集中收集反馈再迭代。我最初设计时没有keepPreviousData,也没有maxAge,这些都是在实际使用中被逼出来的。工具最终长成什么样,应该由它需要服务的真实代码形态决定,而不是由一开始的架构幻想决定。如果你也遇到类似的请求状态管理问题,不妨试着从小封装开始,你会发现收益比想象中来得快得多。

相关新闻

继续教育论文AI检测率太高?千笔智能体降AIGC实操指南

继续教育论文AI检测率太高?千笔智能体降AIGC实操指南

搞继续教育论文的朋友&#xff0c;这段时间应该都被“AI检测率”折磨得不轻。明明是自己一个字一个字写出来的初稿&#xff0c;送到学校的学术不端检测系统里一查&#xff0c;系统直接标红“疑似AIGC生成”&#xff0c;轻则打回修改&#xff0c;重则影响答辩资格。市面上号称能…

2026/10/21 5:19:07 阅读更多 →
从CSV到地图:Lingbot数据坐标转换与WebGIS工程实践

从CSV到地图:Lingbot数据坐标转换与WebGIS工程实践

1. 项目到底在做什么&#xff1a;手里只有一份CSV&#xff0c;却要交出一张能用的地图先说这个项目的来龙去脉。我手头接到一个需求&#xff1a;把某个城市范围的地图相关数据接进来&#xff0c;做一套可以在地图上检索、筛选、叠加展示的应用。听起来很常规&#xff0c;真正麻…

2026/10/21 5:19:07 阅读更多 →
抱歉,我无法提供该内容:错误响应机制与系统可用性设计

抱歉,我无法提供该内容:错误响应机制与系统可用性设计

抱歉&#xff0c;我无法提供该内容。

2026/10/21 5:19:07 阅读更多 →

最新新闻

Elasticsearch集群安全配置:TLS/SSL与密钥库管理全解析

Elasticsearch集群安全配置:TLS/SSL与密钥库管理全解析

给Elasticsearch集群做安全配置这件事&#xff0c;远比想象中麻烦。很多人以为在elasticsearch.yml里加几行TLS/SSL配置就能收工&#xff0c;结果往往是节点之间互相不认证书、Kibana连不上、客户端报handshake failed&#xff0c;最后只能回滚。我这次把一套三节点ES集群从裸H…

2026/10/21 5:49:18 阅读更多 →
嘎嘎降AI实战教程:从注册到多轮降重,让AI文本更自然

嘎嘎降AI实战教程:从注册到多轮降重,让AI文本更自然

"嘎嘎降AI"这名字听起来有点随意&#xff0c;但用起来确实能解决一个实际问题&#xff1a;AI生成的内容&#xff0c;如何改得更像人写的。很多朋友现在写材料、做方案、写公众号&#xff0c;第一稿都习惯让AI对话软件先起个草&#xff0c;然后再自己改。可草稿一到手…

2026/10/21 5:49:18 阅读更多 →
广播、未知、同网、异网:交换机转发与排障核心解析

广播、未知、同网、异网:交换机转发与排障核心解析

干网络这行久了&#xff0c;最常被新人追问的一个问题是&#xff1a;交换机把一个帧从某个端口收进来之后&#xff0c;它凭什么决定从哪个端口送出去&#xff1f;教科书上会讲查MAC地址表、泛洪、丢弃&#xff0c;但真到排障时&#xff0c;大家还是容易懵。后来我发现&#xff…

2026/10/21 5:49:18 阅读更多 →
嘎嘎降AI工具详解:从注册到实操的AI文本自然化处理指南

嘎嘎降AI工具详解:从注册到实操的AI文本自然化处理指南

“你手头有一段AI生成的文章&#xff0c;第一眼读起来特别工整&#xff0c;逻辑也没什么毛病&#xff0c;但总觉得有点‘板’&#xff0c;像戴着面具在说话”——这就是我最初接触到“嘎嘎降AI”这个工具时最想解决的问题。它不是用来做文案初稿的&#xff0c;也不是单纯改错别…

2026/10/21 5:49:18 阅读更多 →
技能管理系统:从技能地图到刻意练习的完整框架

技能管理系统:从技能地图到刻意练习的完整框架

很多人看到“skills”这个词&#xff0c;第一反应是“又一篇讲职场技能提升的文章”。但我今天想换个角度聊&#xff1a;技能不是一个名词&#xff0c;而是一套需要持续维护的系统。你收藏过一堆课程、买过一堆书、报过训练营&#xff0c;最后真正变成自己能力的&#xff0c;其…

2026/10/21 5:49:18 阅读更多 →
AI信息挖掘实战:从提问到交叉验证,把碎片信息串成认知

AI信息挖掘实战:从提问到交叉验证,把碎片信息串成认知

元旦的钟声刚敲过&#xff0c;我猜不少人正把“新的一年多学点东西、把手头的事做精”写进计划表。这几天我发现一个很有意思的现象&#xff1a;大家不是不想查资料&#xff0c;而是被资料淹没了。打开浏览器想看某个行业的最新动态&#xff0c;结果十条链接里八条是去年的&…

2026/10/21 5:48:18 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →