Links: NextJS、React Query
为什么要在 Nextjs 中使用 React Query
在之前的 SPA 项目中,我们一般是在
useEffect
中进行数据获取,在NextJS
项目中,我们尽可能的在服务器组件中获取数据,将获取到的数据通过props
传递给子组件中,一切都没有问题,为什么还要使用 React Query 呢
首先在 useEffect
中获取数据已经是 React 官方所不推荐的方式了,其次通过 React Query,我们能更好的管理请求的状态,而不用自己维护,且 React Query 提供了许多额外的功能来实现请求的功能
配置
- 安装
react-query
依赖
- 创建
QueryClientProvider
- 在
layout
中使用Provider
- 在组件中使用
服务器组件使用
在 NextJS 客户端组件中,使用 React Query
的方式与 SPA React
中没有什么不同,你可以直接使用 useQuery
等并且设置一些查询参数和配置。
但是在 NextJS
中可能遇到与服务器组件有关的一些疑惑,因为 useQuery
无法直接在服务器组件中使用,这样一看好像在 NextJS
中使用 React Query
好像并不是特别必须的了,但其实 React Query
提供了与服务器组件配合使用的能力。
使用 InitialData
与服务器组件一起使用的一种方式是使用 useQuery
的 initialData
属性,它为查询提供初始数据
我们可以依然在服务器组件中提前获取数据,以提高首评渲染的速度,并且将获取到的数据通过 props
属性传递给子组件的 useQuery
中,设置 initialData
的值,useQuery
将其视为未来请求的初始数据,直到数据过期或者从缓存中删除。
你也可以设置initialData
为一个返回数据的函数,用来定制初始数据
设置 initialData
可以影响 useQuery
的 isLoading
状态,当我们没有设置 initialData
时,初始请求时 isLoading
的值为 true
,通常我们在组件内通过这个状态来控制展示一些 Loading
组件,那么效果就是当用户首次进入页面时,就会出现一个 loading
展示,然后数据获取到后展示渲染的数据,体验并不好。
当设置了 initialData
之后,因为有了初始值,所以在用户进入页面初次请求是 isLoading
的状态为 false
,下一次请求时才会变更状态,这样通过在服务器组件中获取数据然后传递给客户端组件,就可以实现页面首次渲染时渲染数据是直接展示的,而不会出现加载中的状态。
如果你希望实现这样的效果,但是又希望能够知道是否在请求中,可以使用 isFetching
字段,它可以用来表示数据是否正在请求,即使数据已经渲染到页面上,isFetching
也可能为 true
,因为 React Query
可能正在后台更新数据,通过这种状态的配合使用,可以很好的提高用户体验。
InitialData 与 PlaceholderData
在 React Query
中设置初始数据其实有两个属性可以使用,一个是 initialData
,另一个是 placeholderData
,这两种方式都可以实现数据的初始填充,但是使用场景和效果略有不同
initialData
被视为真实的数据,会被立即写入缓存,并且一直存在,直到缓存过期,并且会影响isLoading
的值,适用于在 SSR 中或者父组件中通过props
传递的数据。placeholderData
是临时使用的假数据,只在加载请求之间显示,当获取到真实到数据后,placeholderData
会被替换,它不会影响isLoading
的状态,一般使用于需要优化页面加载状态的场景,例如 骨架屏 等
staleTime 和 initialData
在默认情况下我们使用 initialData
可能会有一个疑惑,就是为什么我设置了 initialData
,它已经有了初始值了,为什么在页面渲染的时候,会立即执行一次?如果初始值是从服务器组件中传入的,那么就相当于打开页面时请求了两次,服务器组件请求一次,客户端组件中请求一次,如果你希望实现在有初始数据的情况下第一次加载不发送请求,可以配合 staleTime
属性实现
staleTime
用于控制数据的缓存有效期,超出设置的时间之后数据就会变成不新鲜数据,React Query
就会在下次访问时重新获取数据,它的默认值为 0
, 则会在挂载时理解执行请求
我们设置 staleTime
的值为 10000
,也就是表示数据的有效期为 10秒
,在 10秒
内我们切换页面或者重新触发 useQuery
,都不会发起请求,只有超出这个时间之后,才会重新发起,这个值适合那些不需要即时性的数据展示可以通过与 initialData
一起使用来达到想要的效果。
设置 queryKey
queryKey
在 react-query
中用于标识一个缓存的数据,它必须是唯一的,如果大的项目情况下,每次指定一个字符串 queryKey
很容易导致设置重复,所以最好的方式是对每一个请求抽取一个公用函数,在需要的地方统一使用这个请求函数,并且可以设置请求的 path
作为 queryKey
的值。
另外,可以将 queryKey
视为 useEffect
的依赖项,当 queryKey
的值发生变化时,useQuery
会重新执行,避免使用复杂的逻辑来手动触发重新获取
后台重新获取数据出错时处理
我们可以给 useQuery
设置以下属性来实现自动重新获取
refetchOnMount
- 组件每次挂载时refetchOnWindowFocus
- 窗口每次获取焦点refetchOnReconnect
- 网络重新连接时
正常情况下我们的逻辑会先判断请求是否成功,如果错误了,则会渲染一个错误的页面,成功的话就渲染数据。
考虑一个场景就是,用户切换到其他窗口,再切换回来时,如果我们设置了 refetchOnWindowFocus
,这时候 useQuery
会在后台执行重新请求,如果这次请求失败了,那么展示给用户的效果就是用户一切换到当前窗口,页面就渲染了一个错误页面,在某些场景下这种情况会给用户造成很大的困扰,如果你不希望出现这种情况,可以使用 data
字段,useQuery
在请求失败时,如果有已经缓存的数据存在,则会返回已经缓存的数据,大致结构如下
请求状态为 error
,但是 data
属性有值(缓存的之前的数据),我们可以利用这一点来优化用户体验,就是将数据的判断提前,如果有 data
存在,则渲染数据,否则判断是否错误,在展示错误的页面
queryFn 内联函数优化
很多网上的案例都是这么使用的
可以看到问题存在: queryKey
依赖项与 fetchTodos
的参数不一致,这就导致,当 sorting
参数变化时,useQuery
并不会执行
最好的方式是使用 QueryFunctionContext
, 它会将 queryKey
作为参数传递给 queryFn
不喜欢 queryKey[0]
、queryKey[1]
的参数获取方式,可以给 queryKey
传入一个对象
参考
- [Initial Query Data | TanStack Query React Docs](https://tanstack.com/query/latest/docs/framework/react/guides/initial-query-data
- Placeholder and Initial Data in React Query | TkDodo’s blog