职责:
- 在 内部直接调用
queryFn(写副作用)虽然能跑通,但它被认为是一种反模式 (Anti-pattern),因为它违反了“关注点分离”原则。setAuth
为什么不建议在 queryFn
里写 setAuth
?
queryFnsetAuth-
幂等性问题:
可能会因为网络重试、窗口聚焦刷新、缓存失效等原因被多次调用。在里面写状态更新会导致不必要的多次queryFn。setState -
职责不清:
的唯一职责应该是**“获取并返回数据”。同步 Store、处理重定向属于“响应数据后的副作用”**。queryFn -
调试困难:如果你有多个地方用了
,追踪到底是哪次调用触发了 Store 更新会变得非常混乱。useMe
推荐的“工业级”结构化写法
最具有结构性、让人“一眼看懂”的方案是将逻辑拆分为:纯净的数据层、全局副作用监听、以及受保护的渲染层。
要写出“一眼就能看懂”的工业级代码,理解工具的职责边界是关键。你提到的
QueryClient一、 什么是 QueryClient?
简单来说,QueryClient 是一个“智能数据管家”。
在没有它之前,你需要手动管理
loadingerrordata-
它的含义:它是连接你的 React 组件与远程数据的“中间层”。它不关心你的 API 是用 Eden RPC 还是 Axios 写的,它只关心数据是否过期(Stale)、是否需要重构。
-
形象比喻:它像是一个带缓存的图书馆。
-
是借书请求。
useQuery -
是图书馆管理员。
QueryClient -
如果你要的书(数据)在馆内且没过时,管理员直接给你(缓存);如果过时了,管理员去进货(API 请求)。
-
二、 核心编程规范与职责 (Best Practices)
在现代全栈开发中,遵循**“单一职责原则” (Single Responsibility Principle)** 是最高准则。我们可以把职责划分为四层:
1. 数据层 (The Fetcher) —— 纯净与隔离
-
职责:只负责与后端打交道,把原始数据拿回来。
-
规范:
必须是纯函数。不要在里面写queryFn,不要在里面写router.push。setAuth -
理由:API 报错或重试不应该直接导致全局状态错乱。
2. 状态层 (The Store) —— 单一事实来源
-
职责:存储经过加工的、全应用共享的业务状态(如用户信息、当前站点)。
-
规范:Store 只接收数据并保存,不负责“去哪里拿数据”。
-
理由:这样你的组件无论从哪个 Hook 拿数据,看到的都是同一份结果。
3. 拦截层 (The Provider/Middleware) —— 副作用中心
-
职责:监听数据变化,触发后续动作(同步 Store、持久化 LocalStorage、重定向)。
-
规范:所有的“联动”逻辑(例如:拿到了用户 -> 同步到 Store)应该收拢在这里。
三、 推荐的代码结构规范
为了让你项目中的同事“一眼看懂”,建议采用以下职责分配:
📁 文件组织规范:
-
:放所有的
hooks/api/。职责:定义“怎么拿数据”。useQuery -
:放 Zustand。职责:定义“数据怎么存”。
stores/ -
:放逻辑守卫。职责:定义“数据回来后干什么”。
providers/
💡 代码编写模版(职责分明版):
代码职责:获取数据 (API Hook)
TypeScript
// hooks/api/use-user.ts export const useMe = () => useQuery({ queryKey: ['me'], queryFn: () => rpc.api.v1.me.get().then(handleEden), // 规范:只定义获取逻辑和缓存策略 });
代码职责:处理副作用 (Provider)
TypeScript
// providers/user-provider.tsx export function UserProvider({ children }) { const { data, error } = useMe(); const setAuth = useAuthStore(s => s.setAuth); // 规范:使用 useEffect 统一处理“数据到达”后的动作 useEffect(() => { if (data) { setAuth(data); // 同步到全局 console.log("✅ 用户数据已同步至 Store"); } }, [data, setAuth]); // 规范:统一处理认证失败 if (error?.status === 401) return <Redirect to="/login" />; return <>{children}</>; }
四、 总结:为什么要这么麻烦?
如果你把所有逻辑(API + Store + Router)都塞进
queryFn-
难以测试:你想测试 API 逻辑,却被迫触发了页面跳转。
-
难以重用:另一个组件只想偷偷拿一下数据,结果它一调用,全局 Store 被重置了。
-
调试地狱:当 Store 里的数据错了,你不知道是哪次 API 请求触发的副作用。
遵循规范后的感受:
-
当数据没拿到:去看
。hooks/api/ -
当权限按钮没出来:去看
。stores/auth-store.ts -
当页面没跳转:去看
。UserProvider.tsx
这就是所谓的“结构性”——每个文件只讲一个故事。
你想让我针对你目前的权限系统,帮你优化一下那个
PermissionGuard