React:使用 RTK Query 优雅的获取数据
Redux 还活着吗?
随着 SPA 应用的出现,前端需要管理大量的客户端状态与异步数据获取状态。大量的状态管理逻辑与 UI 交互逻辑耦合,项目变得难以维护。
Redux 的出现相当程度的缓解了这一现象。在 Redux 的核心思想中,动作是改变状态的唯一因素 提高了我们的状态的可控性,加之 时间旅行
的特性,可以很方便的调试应用状态。
动作可以理解为
命令
与事件
,一个动作可能会影响多个领域。如果你了解过 CQRS 与 Event Sourcing,你可能会觉得这些概念很相似
Redux 的诟病也很明显,那就是样板代码太多、文档难以理解,缺少开箱即用的工具。“我仅仅是想发送一个请求,但我却需要在好几个文件中穿越,编写大量的代码”。因此,也越来越多的人开始选择 Mobx
进行状态管理。
在 Redux 作者 Dan 的提议下,Redux Toolkit(简称 RTK)作为官方推荐的最佳实践出现了。样板代码的减少,Slice 概念的出现,加之完善的开发者工具,我们可以十分容易的编写可维护的客户端状态。
异步状态的难题
尽管我们可以编写易于维护的客户端状态,但我们发现,如果要编写一个完善的从服务器加载数据的逻辑,即使有 createAsyncThunk
的帮助,我们仍需手写大量的代码。
从服务器加载数据
缓存数据,避免重复请求
在合适的时机使缓存失效
数据加载指示器
异常错误处理
在不同组件间共享
分页、延迟加载等复杂逻辑
GC,回收不使用的数据
尽可能早的在用户界面上反映数据更新
......
仅仅是一个简单的请求,却可能引出大量复杂的逻辑。
在内部的 toB 应用或许还好,每次涉及到数据访问重新获取即可,逻辑也十分简单。但面向市场,用户体验十分重要,因此上述列表中的随便几点可能就能将我们压垮。
RTK Query 是什么
随着 react-query
库的出现,React 社区意识到,客户端状态管理、异步数据获取与缓存,是两个不同的关注点,Redux 社区也意识到了这一点,RTK Query 基于 Redux 与 RTK,重点关注数据获取与缓存逻辑,目的是解决大部分的数据获取与缓存的常用用例。
入门
你可以在 codesandbox 观察 RTK Query 的缓存行为。
使用 RTK Query 很简单,第一步是定义端点,以声明的方式描述。
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { Pokemon } from './types' export const pokemonApi = createApi({ reducerPath: 'pokemonApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }), endpoints: (builder) => ({ // 定义端点 getPokemonByName: builder.query<Pokemon, string>({ // name 是参数,生成传递给 fetch 的参数 query: (name) => `pokemon/${name}`, }), }), }) // 自动生成的 React Hooks export const { useGetPokemonByNameQuery } = pokemonApi 复制代码
很神奇的特性,RTK Query 根据端点的名称生成的 useQuery
hook,并且具有良好的 TypeScript
代码提示!
然后就可以在 React 中使用了:
function App() { const [name, setName] = useState<string>('xxx') const { // 数据 data, // 错误 error, // 是否正在加载数据(没有缓存过,第一次获取) isLoading, // 是否正在获取数据 isFetching, // 是否成功 isSuccess, // 是否有错误 isError, // 重新获取数据的函数 refetch, } = useGetPokemonByNameQuery(name) if (isLoading) { return <div>loading...</div> } if (isError) { return <div>查询出错:{error.message}</div> } if (!data) { return null } return ( <div> <button onClick={refetch}>重新加载宝可梦数据</button> <Pokemon pokemon={data} /> </div> ) } 复制代码
缓存策略
什么是相同查询
RTK Query 会将查询查询参数序列化为字符串,并将相同端点、相同参数的查询视为相同的查询,他们将共享一个请求与缓存数据。
因此,下面两个调用返回结果相同(即使在不同的组件中):
useGetXXXQuery({ a: 1, b: 2 }) // 订阅 + 1 useGetXXXQuery({ b: 2, a: 1 }) // 订阅 + 1 // ... 复制代码
这是因为:
他们使用相同的查询:GetXXX
查询参数的序列化结果相同:
'{"a":1,"b":2}'
你不需要担心嵌套或是字段顺序,或是不同对象不同引用会被认为是不同的查询,因为 RTK Query 已经在默认的序列化函数中处理了相关用例。同时,你也可以提供自己的序列化函数。
如图,这些妙蛙种子组件中都使用了 useGetPokemon('bulbasaur')
,所以他们将共享同一份数据,并且再次添加新的妙蛙种子时,通过开发者工具可以看到请求被条件取消,因为妙蛙种子的数据已经被加载,在一定的生命周期中可以直接复用缓存。
引用计数与垃圾回收
当在组件中使用某个查询时,该查询的引用计数会 + 1,当该组件被卸载时,引用计数会 -1。当一个查询的引用计数为 0 时,说明没有任何组件在使用这个查询。此时,经过 keepUnusedDataFor
(默认为 30 )秒后,如果缓存仍为被使用过,那么他将被从缓存中移除。
缓存标签
缓存标签可以说是 RTK Query 缓存的核心之一,利用标签,可以实现自动缓存失效,自动重新获取失效数据 等特性。
对于某个 查询
,我们可以为其提供一个标签,如:
{ type: 'Pokemon', id: 'xxx', } 复制代码
然后,我们可以通过 突变
使这个标签失效,例如,我们使用突变 UpdatePokemonByName({ name:'xxx', ...body })
告知 RTK Query 提供了上面这个标签的所有查询缓存都失效了,如果 GetPokemons()
和 GetPokemonByName('xxx')
两个查询均提供了这个标签,那么这两个查询会自动重新获取数据。
例子
在端点定义中,有两种类型的端点:
查询:获取并缓存数据,如
GetPokemonByName
突变:更改数据的动作,可能使缓存无效,如
UpdatePokemonByName
export const pokemonApi = createApi({ reducerPath: 'pokemonApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }), // 声明标签类型 tagTypes: ['Pokemon'], endpoints: (builder) => ({ // 定义查询 getPokemonByName: builder.query<Pokemon, string>({ query: (name) => `pokemon/${name}`, // 查询接口提供的标签 providesTags: (pokemon) => [{ type: 'Pokemon', id: pokemon.name }], }), // 定义突变 updatePokemonByName: builder.mutation<Pokemon, Partial<Pokemon>>({ query(data) { const { name, ...body } = data return { url: `post/${name}`, method: 'post', body, } }, // 更新会使提供了这些标签的查询失效... invalidatesTags: (result, error, arg) => [ { type: 'Pokemon', id: arg.name }, ], }), }), }) 复制代码
通过标签系统,RTK Query 做到了数据失效时机的声明以及自动的重新获取数据。
时间旅行
因为 RTK Query 基于 Redux,所以可以直接使用 Redux DevTools 观察数据获取与缓存行为,同时你可以轻松回到任意时间点,观察组件的运行状态是否正常,例如查看 Loading 状态的指示器样式是否正常。
例如上图,从最终态跳转到皮卡丘的加载态,可以看到按钮变成了 Fetching...
更多例子
RTK Query 提供了一些常见用例的在线 CodeSandbox,可以在这个网址查看:Examples
学习 RTK Query
如果这篇文章让你对 RTK Query 产生了好奇,你可以通过 RTK Query Overview 文档进行深入学习,探索 RTK Query 剩余的 90% 的特性。
作者:Grapes
链接:https://juejin.cn/post/7025651870937022494