体验 TanStack Query 遇到了问题
2023年6月22日 · 252 字 · 2 分钟 · Fetch Deduplication Server State Management
前言
首先,TanStack Query 是什么?它是一个异步状态管理库,最常用于服务端状态的管理。它提供了请求去重、缓存、后台更新过期数据等功能,且开发者不需要考虑缓存数据的垃圾回收等问题。开发者获得一些好的开发体验,用户获得更流畅的使用体验。
我们可以把它看作 useFetch() 的强化版。最常使用的 API 见这段代码:
const {isLoading, error, data, refetch} = useQuery({
queryKey: 'getList',
queryFn: getList
});
问题
虽然本人的使用时间较短,使用范围较小,但确实遇到了一些问题,所以本文就谈一下。
用户层面
-
对用户来说,由 TanStack Query 加持的网站打破了其以往浏览网页时的心智模型。至少对于我这种拥有用户兼开发者双重身份的人来说,打开控制台浏览网页已是司空见惯,所以当我进入一个页面而没有看到 Network 发送请求时,难免怀疑是不是网页出了BUG,怀疑我看到的内容是不是最新的,进一步如果内容不是最新的,会继续怀疑 F5 刷新网页后会是最新的吗?即便不开控制台,那么进入页面后没有出现 loading 效果也足以引发怀疑——“这跟我以前的上网体验不一致”,于我而言,进入网页后有个短暂的 loading 效果并不是什么减分项,这早已是铁的现象,更何况随着网速越来越快,loading 的时间也越来越短。而遇到没有 loading 效果的网页时,我第一反应只会是怀疑而不是感到哇塞。TanStack Query 赋予了开发者掌控缓存的能力,同时也剥夺了有着轻量强迫症的用户对自己上网体验的掌控感。
开发者通过“对不同 URL 区分不同的缓存策略”、搭配着“使用 isFetching 作为指示器提示给用户” 或许可以缓解这个问题,但这也提高了开发成本。
开发者层面
-
使用 TanStack Query 对开发者有一点点门槛,这个门槛就是:需要开发者曾使用过
useFetch
、useRequest
这类 hooks(composables),或者自己尝试过封装一个简陋的useFetch
。综合讲其实就是需要开发者比较熟悉 Composition API。以我的眼光看,现今还有不少开发者没有达到这个门槛,他们仍在中大型项目中使用命令式的请求方式(即 fetch 或 axios ),不仅制造了大量冗余代码,而且每次都手动维护
isLoading, isError, error, data
等状态,降低了开发体验。命令式的优点是方便由用户行为来触发请求,毕竟永远只需要 “onClick=fetch” 就可以了,但这也造就了初次体验 TanStack Query 的开发者不明白为什么要在 setup 中使用 useQuery, 也不太懂 useQuery 为什么会需要 enabled、immediate 这类参数。
-
TanStack Query 虽然提供了 Dependent Queries 的文档,但假如要在 useQuery 之后接续其他非 useQuery 的任务时,最佳实践就要由开发者花费时间探索了(比如我就探索出一种奇怪且愚蠢的处理方式:
watchEffect( () => { if(isSuccess){...} })
)。一个很常见的需求就是在数据获取成功后展示一条 Toast 提示,一些人建议把相关逻辑放在 onSuccess 这个 callback 中。但 Tanstack Query 文档已将那几个 callback 参数标记为 deprecated, 并将在 V5 版本中移除,作者在这里表明了为什么,并阐述了他认为的好的实践。
另外:我看了作者的多篇文章,作为一个没有使用过 React 的人,发现 React 的 useEffect 好像造成了很多混乱,就连我认为的一个平均水平稍高的前端群组也经常有人讨论 useEffect 的最佳实践,我不太想在探讨 TanStack Query 的时候去学习 React 的基础知识。
-
因为缓存数据是 readonly 的,开发者不可以修改 cache 数据,所以会导致开发习惯的变化:
假设网页需要渲染一个从 API 获取的 list,允许用户通过点击来折叠或展开内容。那么在没有使用 TanStack Query 之前,我可能直接就在获取 list 之后为每个 item 追加一个 collapsed 属性,并动态地修改它。在使用了 TanStack Query 之后,开发者则需要多维护一个 map 来表示 item 的折叠状态,开发体验有种降低的感觉。
-
useQuery 提供的返回值并不完全符合开发需求,比如我自己封装的 useFetch 就暴露了 i18n errorMessage 出来,所以开发者还要基于 useQuery 二次封装。
-
维护成本。我不认为 TanStack Query 会在你的项目中完全替代 useFetch,除非是完全新开的项目,所以你的项目会可能会同时包含大量 useQuery 和大量 useFetch,以及… fetch。
-
文档不佳,没有 SWR 的文档简洁有力。曾几何时,我认为 TanStack Query 的文档内容用词洒脱,虽然不太好阅读,但有着一种粗犷的、非常 native 的风格,我甚至想摘抄一些词句送给同样追求 native 翻译的老板。但是伴随着粗犷风格而来的,还有粗枝大叶:
- 因为它适配了多个前端框架,所以你在 Vue 的版本里还经常可以看到 React 版本的配置描述。
- 有些参数没在文档中列出来,导致我在寻找 queryClient 的 defaultOptions 参数文档时,只能依据 windows focus refetch 的那页的内容去猜。
-
Vue Query 不是一等公民。作者发文讨论问题、阐述最佳实践的时候,总是以 React Query 举例,虽然思想相同,API 也没什么区别,但难免有照料不到 Vue 的时候。比如 React 的实现提供了 queryClientProvider 组件,这使得 React 开发者能自定义 queryClient。而 Vue 则没有提供 Provider 组件,只能以全局注册插件的方式来集成,而全局注册因为不处于 setup 上下文中,所以无法在初始化时使用其他 hooks。
-
特殊情况下的状态检测。这一点算不上缺点,但确实需要开发者花费脑力去权衡利弊。比如:首次请求成功并展示了数据,然后 focusout 后重新 focus 触发了 refetch, 此时请求失败。这时 staled data 与 error 并存,展示哪一个,或者同时展示?这取决于开发者想带给用户的体验。参考链接
-
Error 只会迟到,而不会缺席。Retry 的指数避退策略其实是有点唬人的,我从来没听说过第一次请求失败了,然后多重试了几次后就成功了的,除非用户正处于与指数避退算法同步照应的网络环境下。(PS. 该不会有人用 TanStack Query 来抢票或者查高考成绩吧)
总结
个人感觉,在不想增加太多心智负担的情况下,TanStack Query 其实更适合偏现代、轻量、经过良好设计的应用。如果你不想折腾,可以暂时不用,除非你们小组已经形成共识——要在用户体验上面花大功夫。
虽然但是,说不定在还未开始尝试之前,它就被淘汰了😅,作者似乎也曾因 RSC 的出现而透露出一股俏皮且随遇而安的恐慌(虽然 RSC 口碑也不咋样),详见 You Might Not Need React Query。
本文仍会不断补充。