当2018年GraphQL特地是Apolllo Client开始风行之后,很多人开始认为它将代替Redux,对于Redux是否曾经掉队的问题常常被问到。
我很清晰地记得我过后对这些观点的不了解。为什么一些数据申请的库会代替全局状态治理库呢?这两者有什么关联呢?
已经我认为像Apollo这样的Graphql客户端只能用来申请数据,就像axios一样,你依然须要一些形式来让申请的数据能够被应用程序拜访到。
我发现我大错特错。

客户端状态 vs 服务端状态

Apollo提供的不仅仅是形容所需数据同时获取数据的能力,它同时提供了针对这些服务端数据的缓存能力。这意味着你能够在多个组件中应用雷同的useQueryhook,它只会触发一次数据申请并且依照申请的先后顺序返回缓存中的数据。
这看起来跟咱们(包含很多除了咱们以外的团队)在一些场景应用redux的目标很类似:从服务器获取数据,而后让这部分数据能够在所有中央能够被拜访到。
所以仿佛咱们常常将这些服务端数据当成客户端状态来对待,除了这些服务端数据(比方:一个文章列表,你须要显示的某个用户的详细信息,...),你的利用并不真正领有它。咱们只是借用了最新版本的一份数据而后展现给用户。服务端才真正领有这部分数据。
对于我来说,这给了我一个如何对待数据的新的思路。如果咱们能利用缓存来显示咱们不领有的那局部数据,那么剩下的利用须要解决的真正的客户端状态将大大减少。这使我了解了为什么很多人认为Apollo能够在很多场景代替redux。

React Query

我始终没有机会应用GraphQL。咱们有现成的REST API,并没有遇到冗余申请的问题,目前齐全沟通。并没有足够的理由让咱们转换到GraphQL,特地是你还须要让后端服务配合进行改变。
然而我还是艳羡GraphQL带来的前端数据申请(包含loading和谬误态的解决)的简洁。如果React生态中有类似的针对REST API的计划就好了。
让咱们来看看React Query吧。
由Tanner Linsley在2019年开发的React Query使得在REST API中也能够应用到Apollo所带来的益处。他反对任何返回Promise的函数并且应用了stale-while-revalidate缓存策略。库自身有一些默认行为能够尽可能保证数据的实时性,同时尽可能快的将数据返回给用户,让人们感觉近乎实时的体验以提供优良的用户体验。在这之上,他同时提供了灵便的自定义能力来满足各种场景。

这篇文章并不会对React Query进行具体的介绍。
我认为官网文档曾经对应用和概念进行了很好的介绍,同时也有很多对于这方面的视频,并且Tanner开了一门课程能够让你相熟这个库。
我将会更多的关注在官网文档之外的一些实际上的介绍,当你曾经应用这个库一段时间之后,兴许这些介绍对你会有所帮忙。这其中有一些我过来几个月在深度应用React Query以及从React Query社区中总结出的教训。

对于默认行为的解释

我置信React Query的默认行为是通过三思而行的,然而他们有时会让你措手不及,特地是刚开始应用的时候。
首先,React Query并不会在每次render的时候都执行queryFn,即便默认的staleTime是0。你的利用在任何时候可能会因为各种起因从新render,所以如果每次都fetch是疯狂的!
如果你看到了一个你不心愿的refetch,这很可能是因为你刚聚焦了以后窗口同时React Query执行了refetchOnWindowFocus,这在生产环境是一个很棒的个性:如果用户在不同的浏览器tab之间切换,而后回到了你的利用,一个后盾的refetch会被主动触发,如果在同一个工夫服务端数据产生了变更,那屏幕上的数据会被更新。所有这些会在看不到loading态的状况下产生,如果数据和缓存中的数据比照没有变动的话,你的组件不会进行从新render。
在开发阶段,这个景象会呈现得更加频繁,特地是当你在浏览器DevTools和你的利用之间切换的时候。
其次,cacheTimestaleTime的区别仿佛常常让人感到困惑,所以让我来阐明一下:

  • StaleTime:一个查问变成生效之前的时长。如果查问是无效的,那么查问就会始终应用缓存中的数据,不会进行网络申请。如果查问是处于生效状态(默认状况下查问会立刻生效),首先依然会从缓存中获取数据,然而同时后盾在满足肯定条件的状况下会发动一次查问申请。
  • CacheTime:查问从变成非激活态到从缓存中移除继续的时长。默认是五分钟,当没有注册的观察者的时候,查问会变成非激活态,所以如果所有应用了某个查问的组件都销毁的时候,这个查问就变成了非激活态。
    大多数状况下,如果你要扭转这两个设置其中的某一个的话,大部分状况下应该批改staleTime。我很少会须要批改cacheTime。在文档外面也有一个对于这个的解释。

应用React Query DevTools

DevTools会帮忙你更好的了解查问中的状态。它会通知你以后缓存中的数据是什么,所以你能够更不便的进行调试。除了这些,我发现在DevTools中能够模仿你的网络环境来更直观的看到后盾refetch,因为本地服务个别都很快。

把query key了解成一个依赖列表

我这里所说的依赖列表是类比useEffect中说到的依赖列表,我假如你曾经对useEffect曾经比拟相熟了。
为什么这两者会是类似的呢?
因为React Query会触发refetch当query key发生变化。所以当咱们给queryFn传了一个变量的时候,大部分状况下咱们都是心愿当这个变量发生变化的时候能够申请数据。相比于通过简单的代码逻辑来手动触发一个refetch,咱们能够利用query key:

type State = 'all' | 'open' | 'done'type Todo = {  id: number  state: State}type Todos = ReadonlyArray<Todo>const fetchTodos = async (state: State): Promise<Todos> => {  const response = await axios.get(`todos/${state}`)  return response.data}export const useTodosQuery = (state: State) =>  useQuery(['todos', state], () => fetchTodos(state))

这里,设想咱们的UI显示了一个带有过滤器的todo列表。咱们会有一些本地状态来存储过滤器的数据,当用户扭转了过滤条件之后,咱们会更新本地的状态,而后React Query会主动触发一个refetch,因为query key产生了变动。咱们最终实现了过滤状态和查问函数的同步,这与useEffect中的依赖列表很类似。我素来没有没有呈现过给queryFn传了一个变量,然而这个变量不是queryKey的一部分的状况。

一个新的缓存入口

因为query key被用作缓存的key,所以当你把状态从all改成done的时候,你会失去一个新的缓存入口,当你第一次切换过滤状态的时候,会导致一个强制的loading状态(很可能会限度一个loading动画)。这当然不是最现实的,所以你能够应用keepPreviousData来解决这种状况,或者你能够应用initialData来为新的缓存入口预填充数据。下面那个例子能够很完满的解释这个状况,因为咱们能够做一些客户端的数据预过滤:

type State = 'all' | 'open' | 'done'type Todo = {  id: number  state: State}type Todos = ReadonlyArray<Todo>const fetchTodos = async (state: State): Promise<Todos> => {  const response = await axios.get(`todos/${state}`)  return response.data}export const useTodosQuery = (state: State) =>  useQuery(['todos', state], () => fetchTodos(state), {    initialData: () => {      const allTodos = queryClient.getQueryData<Todos>(['todos', 'all'])      const filteredData =        allTodos?.filter((todo) => todo.state === state) ?? []      return filteredData.length > 0 ? filteredData : undefined    },  })

当初,每次用户切换过滤条件的时候,如果咱们没有数据,咱们会尝试用'all todos'缓存中的数据来预填充。咱们能够马上就显示'done'的todo给用户,他们能够在后盾fetch完结之后看到更新之后的列表。留神v3版本中,你须要设置initialStale属性来触发一个后盾fetch。
我认为这简略的几行代码能够给你带来很好的用户体验的晋升。

把服务端状态和客户端状态离开

这个观点和我上个月写的文档一样:如果你从useQuery中拿到了数据,不要把这部分数据放到本地状态中。次要的起因是这样会使得React Query所有后盾更新生效,因为复制进去的本地状态不会自动更新。
如果你心愿获取一些默认数据来设置一个表单的默认值,而后应用数据来渲染表单,那是能够的。后盾更新并不会因为表单曾经初始化就疏忽之后更新的数据。所以如果你想打到这个目标,确保通过设置staleTime来防止触发不必要的后盾refetch:

const App = () => {  const { data } = useQuery('key', queryFn, { staleTime: Infinity })  return data ? <MyForm initialData={data} /> : null}const MyForm = ({ initialData} ) => {  const [data, setData] = React.useState(initialData)  ...}

enabled属性是很弱小的

useQuery hook有很多属性能够用来自定义他的行为,enabled属性是很弱小的一个,它能够让你做很多有意思的事件。上面是一些咱们能够利用它来实现的性能:

  • 依赖查问

在一个查问中获取数据,而后第二个查问只有当咱们胜利的从上一个查问中获取数据的时候才会触发

  • 开启/敞开查问

假如咱们有一个定时查问,通过refetchInterval来实现,然而当一个弹窗关上的时候咱们能够暂停这个查问,防止弹窗前面的内容产生变更。

  • 期待用户输出

比方咱们有一些过滤条件作为query key,然而当用户还没进行过滤操作的时候能够不进行查问。

不要把queryCache当成本地状态管理器

如果你要批改queryCache,它应该只产生在乐观更新或者在变更之后拿到后盾返回的新数据的时候。记住任何一个后盾refetch都会笼罩这些数据,所以能够应用其余本地状态治理库

创立自定义hook

即便你只是封装一个useQuery调用,创立一个自定义hook通常状况下也是值得的,因为:

  • 你能够把实在的数据获取逻辑和UI拆散,过后把它和useQuery调用封装在一起
  • 你能够把对于某个query key的应用都放在同一个文件外面
  • 如果你须要批改一些设置或者减少一些数据转换逻辑,你能够在一个中央进行
    在下面的todo例子外面曾经有一些应用场景。

我心愿这些实践经验能够帮忙你相熟React Query,去试试吧。

原文地址:https://tkdodo.eu/blog/practi...