用react-query解决你一半的状态管理问题
按照来源,前端有两类 「状态」 需要管理:
- 用户交互的中间状态
- 服务端状态
在陈年的老项目中,通常用 Redux
、 Mobx
这样的 「全局状态管理方案」 无差别对待他们。
事实上,他们有很大区别:
用户交互的中间状态
比如组件的 isLoading
、 isOpen
,这类 「状态」 的特点是:
- 以 「同步」 的形式更新
- 「状态」 完全由前端控制
- 「状态」 比较独立(不同的组件拥有各自的
isLoading
)
这类 「状态」 通常保存在组件内部。
当 「状态」 需要跨组件层级传递,通常使用 Context API
。
再大范围的 「状态」 会使用 Redux
这样的 「全局状态管理方案」 。
服务端状态
当我们从服务端请求数据:
function App() { const [data, updateData] = useState(null); useEffect(async () => { const data = await axios.get('/api/user'); updateData(data); }, []) // 处理data }
返回的数据通常作为 「状态」 保存在组件内部(如 App
组件的 data
状态)。
如果是需要复用的通用 「状态」 ,通常将其保存在 Redux
这样的 「全局状态管理方案」 中。
这样做有2个坏处:
- 需要重复处理请求中间状态
为了让 App
组件健壮,我们还需要处理 请求中
、 出错
等中间状态:
function App() { const [data, updateData] = useState(null); const [isError, setError] = useState(false); const [isLoading, setLoading] = useState(false); useEffect(async () => { setError(false); setLoading(true); try { const data = await axios.get('/api/user'); updateData(data); } catch(e) { setError(true); } setLoading(false); }, []) // 处理data }
这类通用的中间状态处理逻辑可能在不同组件中重复写很多次。
- 「缓存」 的性质不同于 「状态」
不同于交互的中间状态,服务端状态更应被归类为 「缓存」 ,他有如下性质:
- 通常以 「异步」 的形式请求、更新
- 「状态」 由请求的数据源控制,不由前端控制
- 「状态」 可以由不同组件共享
作为可以由不同组件共享的 「缓存」 ,还需要考虑更多问题,比如:
- 缓存失效
- 缓存更新
Redux
一把梭固然方便。但是,区别对待不同类型 「状态」 能让项目更可控。
这里,推荐使用 React-Query
管理服务端状态。

另一个可选方案是 SWR [1]。你可以从 这里 [2]看到他们的区别
初识React-Query
React-Query
是一个基于 hooks
的数据请求库。
我们可以将刚才的例子用 React-Query
改写:
import { useQuery } from 'react-query' function App() { const {data, isLoading, isError} = useQuery('userData', () => axios.get('/api/user')); if (isLoading) { returnloading; } return (
-
{data.map(user =>
- {user.name} )}
React-Query
中的 Query
指一个异步请求的数据源。
例子中 userData
字符串就是这个 query
独一无二的 key
。
可以看到, React-Query
封装了完整的请求中间状态( isLoading
、 isError
…)。
不仅如此, React-Query
还为我们做了如下工作:
query
数据的 CRUD
由2个 hook
处理:
useQuery useMutation
在下面的例子中,点击 「创建用户」 按钮会发起创建用户的 post
请求:
import { useQuery, queryCache } from 'react-query'; function App() { const {data, isLoading, isError} = useQuery('userData', () => axios.get('/api/user')); // 新增用户 const {mutate} = useMutation(data => axios.post('/api/user', data)); return (
-
{data.map(user =>
- {user.name} )}
但是点击后 userData query
对应数据不会更新,因为他还未失效。
所以我们需要告诉 React-Query
, userData query
对应的缓存已经失效,需要更新:
import { useQuery, queryCache } from 'react-query'; function App() { // ... const {mutate} = useMutation(userData => axios.post('/api/user', userData), { onSuccess: () => { queryCache.invalidateQueries('userData') } }) // ... }
通过调用 mutate
方法,会触发请求。
当请求成功后,会触发 onSuccess
回调,回调中调用 queryCache.invalidateQueries
,将 userData
对应的 query
缓存置为 invalidate
。
这样, React-Query
就会重新请求 userData
对应 query
的数据。
总结
通过使用 React-Query
(或 SWR
)这样的数据请求库,可以将服务端状态从全局状态中解放出来。
这为我们带来很多好处:
- 使用通用的
hook
处理请求中间状态 - 多余请求合并
- 针对缓存的更新/失效策略
-
Redux
等 「全局状态管理方案」 可以更专注于 「前端中间状态」 处理
参考资料
[1]
SWR: https://swr.vercel.app/
[2]
这里: https://react-query.tanstack.com/comparison