最近因为工作的关系,开始接触 React Query[1]。不用不知道,一用真好用!
React Query 是以前的叫法,现在叫 TanStack Query。之所以改名字,是因为这个团队发现,他们可以把这套方案推广到除 React 之外的其他框架中去。
图片
如图所示,目前(2024.06)最新的 v5 版本已经支持包含 React、Vue、Angular 在内的 5 大框架了。
那 React Query 到底是做什么的呢?
笼统地说,React Query 是用来管理接口请求的,包括增删改查所有类型的接口。管理的内容包括响应数据和请求状态,可以让你少些很多样板代码。
另外,一旦学会了 React Query 的使用,那么在其他框架中的应用也是一样,上手就快了。
不过 React Query 学习成本也高,要彻底熟练 React Query 的使用,就要学习很多概念,不过理解这些概念对于我们写出交互友好的页面又极其关键。
于是,我便萌生了写一个 React Query 系列文章的想法。本文就是第一篇,大概谈论它是做什么的,如何使用,有什么能力,后面再一篇一个具体话题单独讨论。
React Query 是从 v3 版本改名字的:
- v3 以前(包括 v3)包名叫 react-query
- 从 v4 开始包名改成 @tanstack/react-query 了
新旧版本改动不多[2],因为我现在用的是旧包,我就那它举例了。
安装 React Query
先创建一个 React 项目。
npm create vite@latest react-query-demos -- --template react
cd react-query-demos
安装 react-query[3] 依赖,启动项目。
npm install react-query
npm install
npm run dev
接下来删除 index.css 中的内容,再修改 App.jsx,注入 React Query 上下文依赖。
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
{/* ... */}
</QueryClientProvider>
)
}
这一步是必须的,后续 React Query 的接口查询和修改等 API 能力都有赖于 queryClient。
快速开始
说了那么多,我们直接上一个 React Query 案例直观感受一下它的使用。
我们创建一个 <Example /> 组件,内容如下:
import { useQuery } from 'react-query'
function Example() {
// 1)
const { isLoading, isError, error, data } = useQuery('repoData', () =>
fetch('https://api.github.com/repos/tannerlinsley/react-query').then(res =>
res.json()
)
)
// 2)
if (isLoading) return 'Loading...'
if (isError) return 'An error has occurred: ' + error.message
// 3)
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{' '}
<strong>✨ {data.stargazers_count}</strong>{' '}
<strong>🍴 {data.forks_count}</strong>
</div>
)
}
速览一遍代码,我们大概就能明白这块代码是用来做数据请求的。
再在 <App /> 中引入。
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
我这里简单解释一下 <Example /> 中的内容:
- React Query 有暴露出一个 useQuery 这个 React Hook 用来进行数据查询
- 值得注意的是,React Query 本身并不提供数据请求能力,useQuery 的第二个参数中返回使用 Fetch API 请求的 Github 仓库数据(Promise 数据)
- useQuery 的第一个参数,则是用来定义请求数据的缓存 key,这个 key 在同一个 queryClient 下唯一。这样在进行第二次请求时,会先直接返回先前的缓存数据,提升用户体验
- useQuery() 返回值非常贴心的为你封装了请求状态,常用的有 isLoading、isError
isLoading 为 true 表示目前处于请求中
isError 表示请求失败,失败消息可以通过 error 获得
当然还有一个 isSuccess,不过不太常用。一般在判断完 isLoading 和 isError 之后,就说明我们已经成功获得了数据,通过 data 渲染结果即可
图片
三个核心概念
React Query 中有 3 个核心的概念[4],分别是:
- 查询(Queries)
- 修改(Mutations)
- 作废缓存(Query Invalidation)
对应 3 个 API:
- useQuery
- useMutation
- queryClient.invalidateQueries()
上面一节,我们就讲解了“查询”的使用。
以上我们讲解了 React Query 所提供的数据能力。
接下来,我们再举一个完成的例子,说明所有 3 个核心概念。
首先,封装 API 请求,我们把它放在 my-api.js 文件中。
export function getTodos() {
return fetch('https://jsonplaceholder.typicode.com/users/1/todos')
.then(response => response.json())
}
export function postTodo(data) {
return fetch('https://jsonplaceholder.typicode.com/todos', {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
})
.then((response) => response.json())
}
借用了 {JSON} Placeholder[5] 的服务,我们封装了获取和更新代办项目的方法:getTodos() 和 postTodo()。
接着,我们写一个 <Todos> 组件,替换之前的 <Example /> 组件。
import {
useQuery,
} from 'react-query'
function Todos() {
// Queries
const query = useQuery('todos', getTodos)
if (query.isLoading) return 'Loading...'
if (query.isError) return 'An error has occurred: ' + query.error.message
return (
<div>
<ul>
{query.data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</div>
)
}
与之前的 <Example> 大同小异,这里渲染的是一个列表。
不过我们还会增加一个添加代办项的功能。
import {
useQuery,
useMutation
} from 'react-query'
function Todos() {
// Queries
const query = useQuery('todos', getTodos)
// 1)
// Mutations
const mutation = useMutation(postTodo)
if (query.isLoading) return 'Loading...'
if (query.isError) return 'An error has occurred: ' + query.error.message
return (
<div>
<ul>
{query.data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
{/* 2) */}
<button
disabled={mutation.isLoading}
onClick={() => {
mutation.mutate({
userId: 1,
title: 'Do Laundry',
completed: false
})
}}
>
Add Todo
</button>
</div>
)
}
- 首先,引入一个 useMutation() Hook,包装添加代办项的接口能力
- useMutation() 的返回值 mutation 跟 useQuery() 的返回值 query 一样,也是一个复合对象
你可以通过 mutation.mutate() 进行实际操作(本例中就是添加代办)
也可以通过 mutation.isLoading/isError 等获得请求状态
最后一个功能就是作废缓存。
function Todos() {
// 1)
// Access the client
const queryClient = useQueryClient()
// Mutations
const mutation = useMutation(postTodo, {
onSuccess: () => {
// 2)
// Invalidate and refetch
queryClient.invalidateQueries('todos')
},
})
// ...
}
- 这里就要通过 useQueryClient() 引入 queryClient 了。
- 作废缓存的时机是在添加完代办后,因为这个时候代办列表变化了,我们调用 queryClient.invalidateQueries('todos') 方法,代入的 'todos' 其实就是查询代办项时使用那个缓存 key。
调用 .invalidateQueries('todos') 就表示通过 GET '/users/1/todos' 拿到的数据已经过期了,要重新请求。
所以你会看到点击“Add Todo”按钮后,控制台还会多发出一个 GET '/users/1/todos' 请求
图片
总结
React Query 本质上帮你管理接口请求的,管理的内容包括响应数据和请求状态,可以让你少些很多样板代码。
值得注意的是,React Query 本身并不提供接口请求能力,你可以通过 Fetch API 或是 axios 这种三方库提供。
React Query 中有 3 个核心的概念,分别是:
- 查询(Queries)
- 修改(Mutations),和
- 作废缓存(Query Invalidation)
对应 3 个 API:
- useQuery
- useMutation,和
- queryClient.invalidateQueries()
文中也都做了简单案例介绍,不过每个概念背后又有跟多其他内容,这就要在后续的文章中覆盖了。
感谢你的阅读,希望对你有所帮助,再见。
参考资料
[1]
React Query: https://tanstack.com/query/v3/docs/framework/react/overview
[2]新旧版本改动不多: https://tanstack.com/query/v4/docs/framework/react/guides/migrating-to-react-query-4
[3]安装 react-query: https://tanstack.com/query/v3/docs/framework/react/installation
[4]React Query 中有 3 个核心的概念: https://tanstack.com/query/v3/docs/framework/react/quick-start
[5]{JSON} Placeholder: https://jsonplaceholder.typicode.com/