ReactQuery 渲染优化示例详解

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

引言

免责声明:渲染优化是所有应用的进阶话题。React Query已经进行了许多性能优化并且开箱即用,大多数时候不需要做更多优化。"不必要的重新渲染"是一个很多人投入大量关注的话题,也是我要写这篇文章的原因。但是我要再一次指出,大部分情况下对于大多数应用来说,渲染优化很可能并没有想得那么重要。重新渲染是一个好事情。它保证了你的应用展示了最新的状态。相比于重复渲染,我更关注由于缺少渲染而导致的渲染错误。对于更多关于这个话题的讨论,可以看下面的内容:

我在第二篇文章介绍select的内容中已经讲了一些关于渲染优化的事情。然而,"为什么在没有任何数据变化的情况下,React Query会渲染两次组件呢"是我平时被问到最多的一个问题。我们让我来尝试深入解释一下。

isFetching

在之前的例子中我说过,下面这个组件只会在todos的length变化时才会重新渲染,其实我只说了一部分事实:

export const useTodosQuery = (select) =>
  useQuery(['todos'], fetchTodos, { select })
export const useTodosCount = () => useTodosQuery((data) => data.length)
function TodosCount() {
  const todosCount = useTodosCount()
  return <div>{todosCount.data}</div>
}

每次发生后台refetch的时候,这个组件都会下面的数据分别进行一次渲染:

{ status: 'success', data: 2, isFetching: true }
{ status: 'success', data: 2, isFetching: false }

这是因为React Query在每个查询中返回了很多基本信息,isFetching就是其中一个。这个属性在请求正在发生的时候会被设置为true。这个在你想要展示一个后台请求的loading标志的时候特别有用。但是如果你不需要,那确实会造成一些不必要的渲染。

notifiOnChange

对于上面说到的这个场景,React Query提供了notifyOnChangeProps参数。他可以在每个场景单独设置来告诉React Query:只在这些属性发生变化的时候再通知我。通过将这个参数设置为['data'],我们可以实现一个新的版本:

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

保持同步

尽管上面的代码可以正常工作,但是它很容易就会造成不同步。如果我们希望针对error进行特殊处理呢?又或者我们需要使用isLoading属性呢?我们不得不确保notifyOnChangeProps属性和我们实际用到的数据保持同步。如果我们忘记将某个数据添加到属性里面,而只监听data属性的变化,当查询返回错误,同时我们也要展示这些错误的时候,我们的组件并不会重新渲染。这个问题当我们把这些属性写死在自定义hook的时候格外明显,因为我们并不知道使用自定义hook的组件实际上会用到哪些数据:

export const useTodosCount = () =>
  useTodosQuery((data) => data.length, ['data'])
function TodosCount() {
  // 🚨 we are using error, but we are not getting notified if error changes!
  const { error, data } = useTodosCount()
  return (
    <div>
      {error ? error : null}
      {data ? data : null}
    </div>
  )
}

就像我在文章开头免责声明中说的,我认为这是比偶尔发生的不必要的重新渲染更坏的事情。当然,我们可以传参数给自定义hook,但是这还是需要手动处理,是否有什么方式可以自动处理这个情况呢?请看:

被追踪的查询

这是我感受特别自豪的一个特性,这也是我对这个库第一个重大的贡献。如果你将notifyOnChangeProps设置为'tracked',React Query会跟踪你在渲染过程中用到的数据,会自动计算依赖列表。最终的效果就跟你手动维护这个列表一样,除了你不用再去关注这个问题以外。你也可以全局开启这个特性:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      notifyOnChangeProps: 'tracked',
    },
  },
})
function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  )
}

利用这个特性,你再也不用考虑重新渲染。当然这个特性也有一些限制,这就是为什么这个特性是一个可选项:

如果你使用对象剩余属性结构的语法的话,最终所有属性都会被追踪。正常的解构语法是没问题的,不要这么做:

// 🚨 will track all fields
const { isLoading, ...queryInfo } = useQuery(...)
// ✅ this is totally fine
const { isLoading, data } = useQuery(...)

被追踪的查询只会追踪render过程中用到的数据。如果你只在effects中用到了这些数据,他们并不会被追踪。

const queryInfo = useQuery(...)
// 🚨 will not corectly track data
React.useEffect(() => {
  console.log(queryInfo.data)
})
// ✅ fine because the dependency array is accessed during render
React.useEffect(() => {
  console.log(queryInfo.data)
}, [queryInfo.data])

被追踪的查询不会在每次render的时候被重置,所以只要你使用了一次某个数据,你就会在整个组件的生命周期内追踪这个数据:

const queryInfo = useQuery(...)
if (someCondition()) {
  // 🟡 we will track the data field if someCondition was true in any previous render cycle
  return <div>{queryInfo.data}</div>
}

结构化共享

一个不同的但是并没那么重要的React Query默认开启的渲染优化是结构化共享。这个特性确保数据在所有地方是引用唯一的。举个例子,假设我们有下面这个数据结构:

[
{ "id": 1, "name": "Learn React", "status": "active" },
{ "id": 2, "name": "Learn React Query", "status": "todo" }
]

现在假设我们将第一个todo转为done,然后进行了一次后台refetch。我们会从后端拿到一个全新的json:

{ "id": 1, "name": "Learn React", "status": "active" },
{ "id": 1, "name": "Learn React", "status": "done" },
{ "id": 2, "name": "Learn React Query", "status": "todo" }
]

现在React Query会尝试对比新老状态,尽可能多的复用老的状态。在上面的例子中,todo数据会是一个新的对象,因为我们更新了一个todo。第一个id为1的对象也会是新的对象,但是对于id为2的对象我们会保持跟对应的旧数据一样的引用-React Query会将他复制一份同样的引用到新的数据,因为这部分数据并没有发生变化。

这使得使用selector进行部分订阅变得特别友好:

// ✅ will only re-render if something within todo with id:2 changes
// thanks to structural sharing
const { data } = useTodo(2)

就像我之前提到的,对于selector来说结构化共享会用到两次:一次是在queryFn返回的结果上,另一次是在selector返回的结果上。在一些场景,特别是数据量比较大的场景,结构化共享会成为一个瓶颈。同时它只能使用在JSON可序列化的数据上。如果你不需要这个优化,你可以通过将`structuralSharing`设为false来关闭这个特性。

以上就是ReactQuery 渲染优化示例详解的详细内容,更多关于ReactQuery 渲染优化的资料请关注脚本之家其它相关文章!

相关文章

  • React Native开发封装Toast与加载Loading组件示例

    React Native开发封装Toast与加载Loading组件示例

    这篇文章主要介绍了React Native开发封装Toast与加载Loading组件,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • 基于Webpack5 Module Federation的业务解耦实践示例

    基于Webpack5 Module Federation的业务解耦实践示例

    这篇文章主要为大家介绍了基于Webpack5 Module Federation的业务解耦实践示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • React中memo useCallback useMemo方法作用及使用场景

    React中memo useCallback useMemo方法作用及使用场景

    这篇文章主要为大家介绍了React中三个hooks方法memo useCallback useMemo的作用及使用场景示例,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2023-03-03
  • React.js源码解析setState流程

    React.js源码解析setState流程

    这篇文章主要为大家介绍了React.js源码解析setState流程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • React使用context进行跨级组件数据传递

    React使用context进行跨级组件数据传递

    这篇文章给大家介绍了React使用context进行跨级组件数据传递的方法步骤,文中通过代码示例给大家介绍的非常详细,对大家学习React context组件数据传递有一定的帮助,感兴趣的小伙伴跟着小编一起来学习吧
    2024-01-01
  • 基于PixiJS实现react图标旋转动效

    基于PixiJS实现react图标旋转动效

    PixiJS是一个开源的基于web的渲染系统,为游戏、数据可视化和其他图形密集型项目提供了极快的性能,这篇文章主要介绍了用PixiJS实现react图标旋转动效,需要的朋友可以参考下
    2022-05-05
  • React Native中WebView与html双向通信遇到的坑

    React Native中WebView与html双向通信遇到的坑

    这篇文章主要介绍了React Native中WebView与html双向通信的一些问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-01-01
  • 使用React Native创建以太坊钱包实现转账等功能

    使用React Native创建以太坊钱包实现转账等功能

    这篇文章主要介绍了使用React Native创建以太坊钱包,实现转账等功能,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-07-07
  • 详解antd+react项目迁移vite的解决方案

    详解antd+react项目迁移vite的解决方案

    这篇文章主要介绍了详解antd+react项目迁移vite的解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • 详解React中的this指向

    详解React中的this指向

    这篇文章主要介绍了React中的this指向的相关资料,帮助大家更好的理解和学习使用React,感兴趣的朋友可以了解下
    2021-04-04

最新评论