关于javascript:ReactQuery系列文章-3-渲染优化

6次阅读

共计 3767 个字符,预计需要花费 10 分钟才能阅读完成。

免责申明:渲染优化是所有利用的进阶话题。React Query 曾经进行了许多性能优化并且开箱即用,大多数时候不须要做更多优化。” 不必要的从新渲染 ” 是一个很多人投入大量关注的话题,也是我要写这篇文章的起因。然而我要再一次指出,大部分状况下对于大多数利用来说,渲染优化很可能并没有想得那么重要。从新渲染是一个好事件。它保障了你的利用展现了最新的状态。相比于反复渲染,我更关注因为短少渲染而导致的渲染谬误。对于更多对于这个话题的探讨,能够看上面的内容:

  • Fix the slow render before you fix the re-render
  • this article by @ryanflorence about premature optimizations

我在第二篇文章介绍 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 来敞开这个个性。
正文完
 0