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

三葉Leaves Author

区分

一句话总结:

  • 读操作,管理数据状态 => TanStack Query

  • 写操作,现代、优雅的数据变更 => Server Action

TanStack Query 更像是 “读操作和状态同步管理器”,像是管家,解决的是「我怎么在客户端管理这份数据(缓存、状态、重新获取)」。

Server Action 更像是一种工具,解决的是「我怎么和服务器对话,封装业务逻辑」。

结合使用

事实上,两者并不相斥,你甚至可以:

把 server action 作为 TanStack Query 的 queryFn 或者 mutationFn 用,相比直接用封装的 fetch 函数,它依然能享受 server action 的那些好处。

  • 读数据:用 TanStack Query 调用一个 Server Action 或 API 获取数据 → 缓存 + 状态管理交给 Query。
1
2
3
4
const { data, isLoading } = useQuery({
queryKey: ["todos"],
queryFn: () => getTodos(), // getTodos 可以是 server action 或 fetch
});
  • 写数据:用 Server Action 做敏感操作(增删改),返回必要结果。

如果你想让客户端缓存同步,配合 TanStack Query 的 useMutation,在 onSuccess 里调用 invalidateQueries

1
2
3
4
5
const mutation = useMutation({
mutationFn: (content: string) => addTodo(content), // addTodo 是 server action
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["todos"] }),
});

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
    2
    revalidatePath("/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 进行许可。
评论