You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
mutationCache: newMutationCache({onSuccess(data,variables,context,mutation): unknown{queryClient.invalidateQueries({predicate: (query)=>// invalidate all matching tags at once// or nothing if no meta is providedmutation.meta?.invalidates?.some((queryKey)=>matchQuery({ queryKey },query))??false,});constawaits=mutation.meta?.awaits;if(Array.isArray(awaits)){returnqueryClient.invalidateQueries({queryKey: awaits},{cancelRefetch: false});}},}),
description: 我喜欢 Tanstack query`但是在使用过程中遇到了 mutation 之后自动 invalidate 数据的问题,于是搜索到一篇博客,本文是这篇博客的翻译内容。
cover: https://images.unsplash.com/photo-1515879218367-8466d910aaa4?q=80&w=2669&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D
tags:
查询
(Query)和变更
(Mutation)就像是一枚硬币的两面。查询
(Query)定义了一个异步资源用于读取,这通常来源于数据获取。而变更
(Mutation)则是用于更新此类资源的操作。当一个
变更
(Mutation)完成时,它很可能会影响查询
(Query)。例如,变更一个issue
很可能会影响issue
列表。因此,有些人可能会觉得奇怪,为什么不把Query
和Mutation
关联起来?原因其实很简单:
TanStack Query
完全没有主观地要求你如何管理自己的资源,并非每个人都喜欢在mutation
之后重新获取数据。有些情况下,我们希望将
mutation
返回的数据手动放进缓存中,以避免一次网络请求。此外还有很多不同的方法来执行invalidate
(无效化)操作。onSuccess
还是onSettled
回调函数中执行invalidate
操作?前者仅在变更成功之后调用,后者则在出错的时候也会调用。invalidations
结束吗?等待invalidate
行为结束的话将会保持mutation
的挂起状态,直到invalidate
结束(通常是refetch
资源数据)。这是可能是一件好事,例如,如果你希望在提交模态框表单之后等待列表数据更新再关闭模态框。但是,如果你确定想要提交成功后立即关闭模态框
,甚至可能会导致用户一开始因为网络请求的原因可能看不到最新的数据而感到惊奇,那么就没必要等待invalidations
结束。由于没有一种普适的解决方案,
Tanstack Query
并不提供对应开箱即用的功能。然而,感谢Tanstack Query
支持全局缓存回调功能,在Tanstack Query
中实现你想要的自动无效化并不难。全局缓存回调(The Global Cache Callbacks)
变更
(Mutations)有回调函数——onSuccess
、onError
和onSettled
,这些回调函数需要在每个单独的useMutation
中定义。此外,这些回调函数也存在于MutationCache
中。每一个queryClient
实例在创建的时候都可以配置一个mutationCache
,这个mutationCache
里的回调函数会在每一个mutation
调用之后执行(并且会在单独定义的mutation
参数中定义的回调之前执行)。如下所示:
这些回调函数接收的参数与
useMutation
中的参数相同,只是它们还会接收变更实例
(Mutation instance)作为最后一个参数。而且与通常的回调函数一样,返回的Promise
会被等待。那么如何借此实现自动
invalidation
呢?我们只需要在全局回调函数内部调用queryClient.invalidateQueries()
就行了:通过上面的五行代码,我们得到了在 Remix(抱歉,React-Router)中相似的行为:在每次提交之后让一切失效。向你致敬,Alex 展示了这一思路:[Alex / KATT 🐱 on X: "@housecor I just invalidate everything on every mutation https://t.co/0TALY8NdrV" / X](https://x.com/alexdotjs/status/1744467890277921095)
但这是不是有点激进了?
可能是,也可能不是。这得看情况。再强调一次,为什么没有内置这个机制,因为有很多方法可以实现这一点。在这里我们需要澄清一点,
无效化并不总是等同于重新获取数据
。Invalidation
仅仅是重新获取所有匹配的活跃查询,并将其余标记为stale
(过期),这样它们在下次使用时会被重新获取。这通常是一个很好且折衷的方案。想想你现在有一个带有筛选条件的
issue
列表,由于你将过滤条件对象作为queryKey
的一部分,每次筛选条件对象变化都会生成不同queryKey
的缓存记录,然而我们每次只渲染一个筛选对象对应的结果。重新获取所有数据会产生大量当前不必要的请求,我们也没法保证用户会修改筛选条件来让查看对应的结果,从而让这些请求有价值。因此,
invalidation
只重新获取我当前在屏幕上看到的内容(活跃的查询),以获取最新的视图,而其余的内容将在需要时重新获取。尝试让特定的查询失效
好吧,精细化验证是有必要的。变更
issue
列表的数据后让个人资料的数据失效从而触发其重新获取个人资料???这真的没什么意义可言。再说一次,这需要权衡考虑。如果你明确知道需要重新获取什么数据,那么精细化控制让哪些
Query
失效是一个很好的实践方案。如果你希望代码尽量简单,宁愿频繁获取多一些数据也不希望错过重新回去的机会,那么重新获取所有匹配的活跃查询,并将其余标记为stale
(过期)也可以。过去,我们经常进行精细化重新验证,结果发现稍后需要添加的另一个资源与使用的失效模式不匹配。在那时,我们不得不遍历所有变化回调函数,看看是否需要重新获取该资源。这样做很麻烦且容易出错。
此外,我们通常为大多数查询使用中等大小的
staleTime
(大约2分钟)。因此,在无关的用户交互后进行失效的影响可以忽略不计。当然,你可以使你的逻辑更复杂,使重新验证更智能化。以下是我过去使用过的一些技术:
将其与
mutationKey
相关联。MutationKey
和QueryKey
之间没有共同点,而且MutationKey
也是可选的。如果需要的话,你可以通过使用MutationKey
来指定应该失效哪些查询,从而将它们关联起来。这样一来就可以通过定义时的
mutationKey
去让对应queryKey
的数据失效,触发重新执行对应的queryFn
请求数据,如果你没有提供mutationKey
,它仍然会让所有匹配的活跃查询失效。根据 staleTime 排除 Queries
我经常通过给查询设置
staleTime: Infinity
来将其标记为“静态”。如果我们不希望这些查询失效,可以设置queryClient
实例的staleTime
设置,并通过断言(predicate)
过滤器来排除它们。确定查询的实际
staleTime
并不是那么简单,因为staleTime
是观察者级别的属性。但这是可行的,我们还可以将断言过滤器与其他过滤器(如queryKey
)结合使用。很棒。使用 meta 选项
我们可以使用
meta
字段来存储关于mutation
的元数据(静态信息)。举个例子,我们可以添加一个invalidates
字段来标记这个mutation
,然后再用这些标记来模糊匹配我们希望失效的查询:如上所示,我们通过
meta
字段让issue
和labels
匹配的项失效,触发其重新获取数据。这里,我们仍然使用谓词函数来进行一次调用到
queryClient.invalidateQueries
。但在函数内部,我们使用matchQuery
进行模糊匹配,你可以从React Query
中导入这个函数。这个函数在将单个queryKey
作为过滤器传递时内部也会使用,但现在我们可以用多个键来进行匹配。这种模式比在
useMutation
的onSuccess
回调函数中调用queryClient.invalidateQueries
更好一些,至少我们不用每次都使用useQueryClient
来引入queryClient
。另外,这种模式还和默认让所有内容失效结合起来,非常灵活。等还是不等?
在上面的例子中我们几乎等待
invalidation
结束,如果你希望你的变更(mutation)尽快结束,那么这就够了。我经常遇到一个具体的场景是希望所有内容都失效,但是却让
mutation
保持挂起的状态,直到其中若干重要的更新完成。例如,我希望变更标签数据后等待特定标签的refetch
完成,但是并不等待其他同样invalidation
的数据重新获取完成。我们可以扩展一下
meta
对象来实现这个需求,举个例子:我们可以在配置
queryClient
的时候,扩展onSuccess
函数,内部检查是否提供awaits
数组来控制返回值。这里我们如果提供了
awaits
内容,就将Promise
返回,useMutation
处定义的onSuccess
函数将会在这个Promise
转为resolve
状态之后才执行,如此一来我们就可以等待指定的query
实例失效并且请求新数据之后再执行自己的onSuccess
回调函数逻辑了。回到原文。
或者,我们可以利用
MutationCache
上的回调在useMutation
上的回调之前运行的机制,配合全局回调设置为使所有内容无效,再添加一个本地回调来等待我们想要的结果:这将会:
Promise
内容,因此这是一个一劳永逸的失效行为onSuccess
回调将会立即执行,我们可以主动返回一个仅让匹配['labels']
查询key
的失效Promise
状态,因此Mutation
将保持待pending
状态,直到重新获取['labels']
为止。我认为这表明添加您熟悉的自动失效抽象并不需要很多代码。请记住,
每个抽象都有成本:它是一个新的 API,需要正确学习、理解和应用。
我希望通过展示所有这些可能性,可以更清楚地了解为什么我们没有在
React Query
中内置任何内容。找到一个足够灵活、能够覆盖所有情况而不臃肿的 API 并不是一件容易的事情。为此,我更愿意为您提供在用户空间中构建此工具的工具。The text was updated successfully, but these errors were encountered: