阅读 504

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 已经在默认的序列化函数中处理了相关用例。同时,你也可以提供自己的序列化函数。

蛤蛤.png

如图,这些妙蛙种子组件中都使用了 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


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐