ReactQuery系列之数据转换示例详解

 更新时间:2022年11月10日 11:19:35   作者:lakb248  
这篇文章主要为大家介绍了ReactQuery系列之数据转换示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

欢迎来到“关于react-query我不得不说的一些事情”的第二章节。随着我越来越深入这个库以及他的社区,我发现一些人们经常会问到的问题。最开始,我计划在一篇超长的文章里面把这些都讲清楚,最终我还是决定将他们拆分成一些有意义的主题。今天第一个主题是一个很普遍但是很重要的事情:数据转换。

数据转换

我们不得不面对这个问题-大部分的人并没有使用GraphQL。如果你使用了,那么恭喜你,因为你可以请求到你期望的数据格式。

如果你在使用REST风格的API,你就必须受限于后端返回的数据格式。所以在使用react-query的时候我们应该在什么地方通过什么方式来进行数据转换呢?

答案只有一个:看情况。

下面列举出四种进行数据转换的方式,以及他们的优缺点:

后端

这是我最喜欢的方式,如果你有决定权的话。如果后端返回的数据结构是你所期望的话,那么你就什么都不用做了。但是在很多场景这并不太现实,比如一些公共的REST API,特别是在企业级应用中。如果你可以让后端针对每一个具体的场景都有一个对应的接口,那么可以返回你期望的数据结构。

  • 优点:
    前端什么都不用做
  • 缺点:
    并不是所有情况下都能做到

查询函数中

查询函数是你传给useQuery的函数。他会返回一个Promise,最终返回的数据会被存在缓存中。但是这并不意味着你只能按照后端给你的数据结构来返回数据。你可以在返回之前进行数据转换:

const fetchTodos = async (): Promise<Todos> => {
  const response = await axios.get('todos')
  const data: Todos = response.data
  return data.map((todo) => todo.name.toUpperCase())
}
export const useTodosQuery = () => useQuery(['todos'], fetchTodos)

之后你就可以在其他地方使用转换之后的数据,仿佛后端返回的数据就是这样的。你在其他地方都不会拿到不是大写的todo名字了。同时你也拿不到数据的原始结构了。如果你查看react-query-devtools,你会看到转换之后的结构。如果你查看网络请求,你可以看到原始的数据结构。这个可能会有点让人感到困惑,所以不要忘了你在代码里面处理了数据结构。

同时,在这里react-query并不会做什么优化。也就是说每一次fetch被执行的时候,你的转换逻辑都会被执行。如果转换逻辑很复杂,需要考虑一下其他转换方式。一些公司在前端会有一个公共的API层来抽象数据获取,所以你可能没办法在这个抽象层里面做你的数据转换。

  • 优点:
    和API调用绑定在一起,对上层无感知
  • 缺点:
    在每次数据请求的时候都会运行
    如果你有一个你无法修改的公共的API层,这个方式不太可行
  • 其他:
    存储在缓存中的是转换之后的数据结构,所以你没办法拿到原始的数据结构

render函数中

正如第一章节中介绍的,你可以自定义一个hook,那么你可以很方便的在这个hook里做数据转换:

const fetchTodos = async (): Promise<Todos> => {
  const response = await axios.get('todos')
  return response.data
}
export const useTodosQuery = () => {
  const queryInfo = useQuery(['todos'], fetchTodos)
  return {
    ...queryInfo,
    data: queryInfo.data?.map((todo) => todo.name.toUpperCase()),
  }
}

正如代码逻辑所示,数据转换不会在每次数据查询的时候运行,但是会在每次render的时候运行(即使这次render并没有触发数据请求)。这看起来这不是什么大问题,如果你在意的话,你可以通过useMemo来进行优化,同时尽可能只定义真正需要的依赖列表。queryInfo中的data是引用稳定的除非数据真的发生了变化,但是queryInfo就不是了。如果你把queryInfo作为你的依赖,那么转换逻辑就会在每次render的时候运行:

export const useTodosQuery = () => {
  const queryInfo = useQuery(['todos'], fetchTodos)
  return {
    ...queryInfo,
    // 🚨 don't do this - the useMemo does nothing at all here!
    data: React.useMemo(
      () => queryInfo.data?.map((todo) => todo.name.toUpperCase()),
      [queryInfo]
    ),
    // ✅ correctly memoizes by queryInfo.data
    data: React.useMemo(
      () => queryInfo.data?.map((todo) => todo.name.toUpperCase()),
      [queryInfo.data]
    ),
  }
}

特别是当你在自定义hook中有一些额外的逻辑来协助进行数据转换的时候,这是一个很好的选择。需要注意的是data有可能是undefined,所以请使用可选链式访问来获取data中的数据。

  • 优点:
    可以通过useMemo进行优化
  • 缺点
    写法有一些晦涩
    data可能会是undefined
  • 其他
    确切的数据结构无法在devtool中展示

使用select配置

v3引入了内置的selector,可以用它来进行数据转换:

export const useTodosQuery = () =>
  useQuery(['todos'], fetchTodos, {
    select: (data) => data.map((todo) => todo.name.toUpperCase()),
  })

selector只会在data存在的时候被调用,所以你不用担心undefiend的问题。像上面的selector会在每次render的时候被执行,因为函数表达式变化了(因为这是一个内联函数)。如果转换逻辑比较复杂,你可以使用useCallback来进行memoize,或者把他抽象到一个稳定的函数引用中:

const transformTodoNames = (data: Todos) =>
  data.map((todo) => todo.name.toUpperCase())
export const useTodosQuery = () =>
  useQuery(['todos'], fetchTodos, {
    // ✅ uses a stable function reference
    select: transformTodoNames,
  })
export const useTodosQuery = () =>
  useQuery(['todos'], fetchTodos, {
    // ✅ memoizes with useCallback
    select: React.useCallback(
      (data: Todos) => data.map((todo) => todo.name.toUpperCase()),
      []
    ),
  })

在未来,select配置也可以被用来订阅data中的部分数据。这使得这一数据转换实现方式变得特别。看看下面这个例子:

export const useTodosQuery = (select) =>
  useQuery(['todos'], fetchTodos, { select })
export const useTodosCount = () => useTodosQuery((data) => data.length)
export const useTodo = (id) =>
  useTodosQuery((data) => data.find((todo) => todo.id === id))

这里,我们创建了一个像useSelector一样的API,你可以传自定义selector到useTodosQuery中。这个自定义hook仍然可以像之前一样工作,如果你没有传select,会返回整个数据。
但是如果你传了selector,你就只会订阅selector返回的部分数据。这是很有用的,因为这意味着如果我们更新了一个todo的名字,只通过useTodosCount订阅了count的组件并不会重新渲染。count没有发生变化,所以react-query可以选择不通知这部分数据的订阅者(注意这里说得很容易,但是具体实现不完全跟这个描述一样,我会在第三部分渲染优化中聊一聊这部分内容)

  • 优点:
    最佳优化
    支持部分订阅
  • 其他:
    每个订阅者的数据可能都不一样

以上就是ReactQuery系列之数据转换示例详解的详细内容,更多关于ReactQuery 数据转换的资料请关注脚本之家其它相关文章!

相关文章

  • 一个基于react的图片裁剪组件示例

    一个基于react的图片裁剪组件示例

    本篇文章主要介绍了一个基于react的图片裁剪组件示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04
  • ahooks正式发布React Hooks工具库

    ahooks正式发布React Hooks工具库

    这篇文章主要为大家介绍了ahooks正式发布值得拥有的React Hooks工具库使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • react+antd select下拉框实现模糊搜索匹配的示例代码

    react+antd select下拉框实现模糊搜索匹配的示例代码

    我们在开发过程中,经常会出现下拉框数据很多得情况,本文主要介绍了react+antd select下拉框实现模糊搜索匹配的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • React解析html 标签的方法

    React解析html 标签的方法

    在React中,解析HTML标签通常是使用JSX(JavaScript XML)语法的一部分,这篇文章主要介绍了React 用来解析html 标签的方法,本文通过示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-08-08
  • React tsx生成随机验证码

    React tsx生成随机验证码

    这篇文章主要为大家详细介绍了React tsx生成随机验证码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-04-04
  • React.js Gird 布局编写键盘组件

    React.js Gird 布局编写键盘组件

    这篇文章主要介绍了React.js Gird 布局编写键盘组件,Grid 布局则是将容器划分成"行"和"列",产生单元格,然后指定"项目所在"的单元格,可以看作是二维布局
    2022-09-09
  • react-draggable实现拖拽功能实例详解

    react-draggable实现拖拽功能实例详解

    这篇文章主要给大家介绍了关于react-draggable实现拖拽功能的相关资料,React-Draggable一个使元素可拖动的简单组件,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
    2023-08-08
  • react diff算法源码解析

    react diff算法源码解析

    这篇文章主要介绍了react diff算法源码解析的相关资料,帮助大家更好的理解和学习使用react,感兴趣的朋友可以了解下
    2021-04-04
  • 利用CDN加速react webpack打包后的文件详解

    利用CDN加速react webpack打包后的文件详解

    下面小编就为大家分享一篇利用CDN加速react webpack打包后的文件详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-02-02
  • react native 仿微信聊天室实例代码

    react native 仿微信聊天室实例代码

    这篇文章主要介绍了react native 仿微信聊天室实例代码,代码简单易懂,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-09-09

最新评论