⚠️ 【待 review】
TanStack Query 被高估了?这 5 个场景它真不如 alova
TL;DR --- 如果你只用 TanStack Query 做基础的「获取数据 + 缓存」,那它确实够用。但当你遇到分页、表单、上传、跨组件通信这些「真实世界的复杂请求场景」时,alova 的策略钩子能让你少写 70% 的样板代码。
先说两句
我用了两年的 TanStack Query,一度觉得它就是前端请求库的终局形态。稳定、社区大、文档好------看起来没什么毛病。
直到我开始做一个中后台管理系统,表单、分页、上传、轮询、跨组件通信......全来了。我才意识到一件事:
TanStack Query 给你的只是一个「增强版 fetcher」,它把所有复杂度都留给了你。
而 alova 给了你一套完整的请求策略。
不是某个特性碾压,而是在完整的产品级场景中,alova 的体验断层式领先。下面我用 5 个真实场景来证明。
场景 1:表单提交
Before --- TanStack Query + React Hook Form
tsx
// TanStack Query 没有原生表单钩子
// 你需要手动管理提交、重置、草稿、验证
const { mutateAsync } = useMutation({
mutationFn: (data) => axios.post('/api/form', data),
})
const { register, handleSubmit, reset } = useForm({
// 手动管理草稿持久化
defaultValues: loadFromDraft()
})
// 监听表单变化存草稿
useEffect(() => {
saveDraft(watch())
}, [watch()])
After --- alova
tsx
const {
loading, submit, updateForm, reset,
form, // 响应式表单数据
} = useForm(
(formData) => alova.Post('/api/form', formData),
{
// 自动草稿持久化,页面刷新后恢复
store: true,
// 提交后自动重置
resetAfterSubmiting: true,
}
)
// 完了。就这么简单。
alova 的 useForm 内置了草稿持久化、多步骤状态共享、提交后自动重置。不需要 useEffect、不需要外部表单库、不需要手动存 localStorage。
如果你做的系统需要 10 个表单,useForm 能让你省掉至少 200 行草稿管理代码。
场景 2:分页 / 无限滚动
Before --- TanStack Query
tsx
// 手动管理页码,手动拼接 keepPreviousData
const [page, setPage] = useState(1)
const { data, isPreviousData } = useQuery({
queryKey: ['list', page],
queryFn: () => fetch(`/api/list?page=${page}`),
keepPreviousData: true,
})
这还只是最基础的。一旦你想要的下一页预加载、编辑某条数据后更新列表、删除某条后从列表中移除------你就得写额外的逻辑:
tsx
const queryClient = useQueryClient()
const prefetchNext = () => {
queryClient.prefetchQuery(['list', page + 1], ...)
}
useEffect(() => {
if (data?.length < pageSize) return
prefetchNext()
}, [page, data])
After --- alova
tsx
const {
data, loading, page, pageSize,
handlePrevPage, handleNextPage,
reload, // 刷新当前页
} = usePagination(
(page, pageSize) => alova.Get('/api/list', {
params: { page, pageSize }
}),
{
preloadNextPage: true, // 自动预加载下一页
total: res => res.total,
pageSize: 20,
}
)
// 编辑后自动更新列表项
const { send: editItem } = useRequest(
(newData) => alova.Put('/api/item', newData),
{
behavior: 'silent',
onSuccess: () => {
// 自动替换列表中的对应项,无需手动查找替换
}
}
)
// 删除后自动从列表中移除
const { send: deleteItem } = useRequest(
(id) => alova.Delete(`/api/item/${id}`),
{
behavior: 'silent',
onSuccess: () => {
// 自动从列表中移除
}
}
)
三个字:开箱即用。
场景 3:自动轮询 + 焦点刷新
Before --- TanStack Query
tsx
const { data } = useQuery({
queryKey: ['notification'],
queryFn: () => fetch('/api/notification'),
refetchInterval: 5000,
refetchOnWindowFocus: true,
})
看起来还行,对吧?但一旦你要控制什么时候轮询、什么时候停止、节流控制------你就开始写「控制轮询的代码」了:
tsx
const [enabled, setEnabled] = useState(false)
const { data } = useQuery({
queryKey: ['notification'],
queryFn: () => fetch('/api/notification'),
refetchInterval: enabled ? 5000 : false,
refetchOnWindowFocus: enabled,
})
After --- alova
tsx
const { loading, data, stop, resume } = useAutoRequest(
() => alova.Get('/api/notification'),
{
enablePoll: true,
pollInterval: 5000,
// 内置智能节流:浏览器不可见时不请求
enableThrottle: true,
// 焦点回归时自动刷新
enableVisibility: true,
}
)
// 按需控制
stop() // 暂停
resume() // 恢复
alova 的 useAutoRequest 把轮询、焦点刷新、节流、可见性控制整合到一个钩子里。你不需要想「我要不要开启轮询」,而只需要想「什么时候轮询、什么时候停」。
场景 4:跨组件请求通信
在真实项目中,往往是 A 组件新增了数据,B 组件需要刷新。或者 C 组件更新了某条记录,D 组件需要知道。
Before --- TanStack Query
tsx
// A 组件
const mutation = useMutation({
mutationFn: (data) => axios.post('/api/item', data),
onSuccess: () => {
// 需要手动 invalidate
queryClient.invalidateQueries({ queryKey: ['list'] })
},
})
// B 组件也用了 'list' 这个 key
// 但如果你有个组件依赖于更新后的单条数据......
问题是:随着组件数量增加,invalidateQueries 调用散落在各处,你很难追踪「谁 invalidate 了谁」。
After --- alova
tsx
// 定义方法实例时用 name 标记
const getTodoList = (keyword) =>
alova.Get('/api/todo/list', {
name: 'todoList',
params: { keyword }
})
const updateTodo = (id, data) =>
alova.Put(`/api/todo/${id}`, data, {
// 命中 'todoList' 的缓存自动失效
hitSource: 'todoList',
})
// 或者用 actionDelegationMiddleware 实现跨组件调用
// A 组件
const { send } = useRequest(updateTodo, {
middleware: actionDelegationMiddleware('updateTodo')
})
// B 组件(非父子关系)
accessAction('updateTodo', ({ send }) => {
send()
})
不需要理解 queryClient、不需要追踪 queryKey 图谱。 声明式的 hitSource 和函数式的 actionDelegationMiddleware 让跨组件通信变得一目了然。
场景 5:文件上传
Before --- TanStack Query
TanStack Query 没有内置的文件上传支持。你需要:
tsx
// 自己封装上传逻辑
const uploadMutation = useMutation({
mutationFn: async (file: File) => {
const formData = new FormData()
formData.append('file', file)
const { data } = await axios.post('/api/upload', formData, {
onUploadProgress: (e) => {
// 手动管理进度状态
setProgress(Math.round((e.loaded * 100) / e.total))
}
})
return data
},
})
上传多个文件?并发控制?暂停/恢复?全部自己写。
After --- alova
tsx
const {
upload, // 触发上传
loading,
progress, // 实时进度
} = useUploader(({ file, name }) =>
alova.Post('/api/upload', {
file, name
})
)
// 多文件并发上传
const { upload } = useUploader(
({ file }) => alova.Post('/api/upload', { file }),
{
limit: 3, // 最多 3 个文件并发
}
)
// 暂停/恢复上传
controller.pause()
controller.resume()
6 个属性就能实现一个完整的文件上传组件。 TanStack Query 根本不做这件事------它假装上传不存在。
总结一下
| 场景 | TanStack Query | alova |
|---|---|---|
| 基础 GET 请求 | ✅ 够用 | ✅ 够用 |
| 表单提价草稿/重置 | ❌ 自己写 | ✅ useForm |
| 分页 + 预加载 + 列表操作 | ❌ 自己写 | ✅ usePagination |
| 智能轮询 + 节流 | ❌ 自己写 | ✅ useAutoRequest |
| 跨组件通信 | ❌ invalidateQueries | ✅ hitSource / actionDelegation |
| 文件上传 + 并发控制 | ❌ 自己写 | ✅ useUploader |
我不是说 TanStack Query 不好。它是个优秀的库。但是在真实产品开发中,当你的请求场景从「获取数据」扩展到「管理数据」时,alova 的策略化方案带来的效率提升是断崖式的。
如果你还只用它做简单的 GET 请求 + 缓存,试试它的策略钩子------你会发现,原来「请求」这件事可以这么简单。