Server Action,TanStack Query ... 我到底要用谁

区分
一句话总结:
-
读操作,管理数据状态 => TanStack Query
-
写操作,现代、优雅的数据变更 => Server Action
TanStack Query 更像是 “读操作和状态同步管理器”,像是管家,解决的是「我怎么在客户端管理这份数据(缓存、状态、重新获取)」。
而 Server Action 更像是一种工具,解决的是「我怎么和服务器对话,封装业务逻辑」。
结合使用
事实上,两者并不相斥,你甚至可以:
把 server action 作为 TanStack Query 的 queryFn 或者 mutationFn 用,相比直接用封装的 fetch 函数,它依然能享受 server action 的那些好处。
- 读数据:用 TanStack Query 调用一个 Server Action 或 API 获取数据 → 缓存 + 状态管理交给 Query。
1 | const { data, isLoading } = useQuery({ |
- 写数据:用 Server Action 做敏感操作(增删改),返回必要结果。
如果你想让客户端缓存同步,配合 TanStack Query 的 useMutation
,在 onSuccess
里调用 invalidateQueries
。
1 | const mutation = useMutation({ |
State 管理
Server Action 配合 useActionState 使用有其 state ,TanStack Query 也有其 state ,甚至 React Form Hook 也有其 state 。这些 state 我到底该用谁,怎么用?
简要总结:
-
输入层 → RHF 管「输入」
-
动作层 → useActionState 管「提交」
-
数据层 → TanStack Query 管「数据」
1. React Hook Form (RHF)
关注点:管理 输入字段 和 校验逻辑。
👉 比喻:RHF 是 「键盘输入监听器 + 校对助手」
1 | const { register, handleSubmit, formState: { errors } } = useForm(); |
场景:
字段值、合法性,输入框校验
例如其中的 isSubmitting 属性,职责是 让表单控件进入「提交中」状态(禁用按钮、防止重复提交),用于 禁用按钮、防抖。
2. Server Action + useActionState
🎯 关注点:管理 一次提交动作 的状态
-
提交时:
isPending = true
(正在传到服务端) -
成功时:拿到返回结果,可能重置表单/显示 toast
-
失败时:返回错误,展示在表单里
1 | const [formState, formAction, isPending] = useActionState(addTodo, initialState); |
场景:提交按钮的 loading、错误消息。
例如其 isPending 属性,职责是让 UI 知道这个 action 还没返回(常用来给按钮加 spinner,展示「提交中…」)。
3. TanStack Query
前两者都和表单有关,这个就只和数据有关。
🎯 关注点:管理 全局/共享数据 的请求与缓存。它的状态不仅仅是「这次请求」,而是「整个缓存的数据生命周期」。
👉 比喻:它是 「数据仓库的管家」,负责 UI 和服务器数据保持同步。
1 | const { data, isFetching } = useQuery({ queryKey: ["todos"], queryFn: getTodos }); |
场景:
提交成功后,invalidateQueries(["todos"])
,Todo 列表自动刷新,UI 跟数据库保持一致。列表刷新 → useQuery.isFetching = true
(列表转圈)
例如其 isPending / isFetching 属性,关心的是 「我的数据源是否在加载 / 刷新?」,而不是某个动作的情况。
视图刷新 & 缓存失效
Next.js 生态里,有三种“刷新数据”的方式:
-
TanStack Query →
invalidateQueries()
-
Next.js Server Action →
revalidatePath("xxx")
-
Next.js Router →
router.refresh()
其中前两者经常需要一起用,因为服务端缓存更新了,客户端没更新也没用,会导致 Hydration mismatch 错误。
1. invalidateQueries
(TanStack Query)
🎯 作用域:客户端,针对特定 queryKey 的缓存
-
告诉 TanStack Query:这份缓存“过期了”,下次需要重新 fetch。
-
不会刷新页面,不会影响别的 query。
👉 适合场景:
-
你用 TanStack Query 管理 列表/详情等客户端数据。
-
比如新增 Todo 后,执行:
1
queryClient.invalidateQueries({ queryKey: ["todos"] })
只会让 todo 列表重新拉取。
2. revalidatePath("xxx")
(Next.js Server Action)
🎯 作用域:服务端,针对指定路径的 SSG/ISR 缓存
-
强制让 Next.js 在服务器端 重新生成某个路径的页面数据。
-
常用于 服务器渲染的数据刷新(SSG/ISR 生成的页面、RSC 数据)。
-
是一个服务端动作,只能在 server action 里用。
👉 适合场景:
-
你在服务端修改了数据,想让某个 RSC 页面或路径马上失效。
-
例如博客文章更新:
1
2revalidatePath("/posts")
revalidatePath(`/posts/${post.id}`)下次访问会重新渲染。
3. router.refresh()
(Next.js 客户端路由)
🎯 作用域:客户端,刷新整个 RSC 树
-
相当于“强制重新跑一次 RSC fetch”,让当前页面的数据都重新请求一遍。
-
粒度比
invalidateQueries
粗,范围比revalidatePath
大。 -
类似浏览器的 F5,但不会丢掉 SPA 状态(还是在同一个页面)。
👉 适合场景:
-
你不想管具体哪部分数据更新,直接“全局刷新”。
-
比如用户登出后,整个页面都要变(头部导航、列表、按钮状态)。
- 标题: Server Action,TanStack Query ... 我到底要用谁
- 作者: 三葉Leaves
- 创建于 : 2025-09-08 00:00:00
- 更新于 : 2025-09-26 18:04:55
- 链接: https://blog.oksanye.com/74a209498b7a/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。