渲染优化

React Query 会自动应用一些优化措施,以确保你的组件仅在真正需要时才重新渲染。这是通过以下方式实现的:

结构共享 (Structural Sharing)

React Query 使用一种称为“结构共享”的技术,以确保在重新渲染之间尽可能多地保持引用不变。如果数据是通过网络获取的,通常你会通过 JSON 解析响应得到一个全新的引用。然而,如果数据没有任何变化,React Query 将会保留原始引用。如果数据的某个子集发生了变化,React Query 将保留未更改的部分,仅替换已更改的部分。

注意:此优化仅在 queryFn 返回与 JSON 兼容的数据时才有效。你可以通过全局或在单个查询上设置 structuralSharing: false 来关闭它,或者通过传入一个函数来实现你自己的结构共享逻辑。

引用标识 (Referential Identity)

useQueryuseInfiniteQueryuseMutation 返回的顶层对象,以及从 useQueries 返回的数组,都不是引用稳定的。这意味着每次渲染时它们都会是一个新的引用。然而,从这些 Hook 返回的 data 属性会尽可能保持稳定。

追踪的属性 (Tracked Properties)

React Query 只有在 useQuery 返回的某个属性真正被使用时,才会触发重新渲染。这是通过使用 Proxy 对象 实现的。这避免了许多不必要的重新渲染,例如,像 isFetchingisStale 这样的属性可能会频繁变化,但如果组件中没有使用它们,就不会触发重新渲染。

你可以通过手动设置 notifyOnChangeProps 来自定义此功能,可以在全局或单个查询上进行设置。如果你想完全关闭此功能,可以将 notifyOnChangeProps 设置为 'all'

select

你可以使用 select 选项来选择数据的一个子集,你的组件将只订阅这个子集。这对于高度优化的数据转换或避免不必要的重新渲染非常有用。

export const useTodos = (select) => {
  return useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    select,
  })
}

export const useTodoCount = () => {
  return useTodos((data) => data.length)
}

使用 useTodoCount 自定义 Hook 的组件,只有在待办事项列表的长度发生变化时才会重新渲染。如果某个待办事项的名称发生了变化,它不会重新渲染。

记忆化 (Memoization)

select 函数仅在以下情况下才会重新执行:

  • select 函数本身的引用发生了变化

  • data 发生了变化

这意味着,像上面那样内联定义的 select 函数,会在每次渲染时都执行。为了避免这种情况,你可以使用 useCallback 来包裹 select 函数,或者将其提取为一个稳定的函数引用(如果它没有依赖项的话):

// 使用 useCallback 包裹
export const useTodoCount = () => {
  return useTodos(useCallback((data) => data.length, []))
}
// 提取为一个稳定的函数引用
const selectTodoCount = (data) => data.length

export const useTodoCount = () => {
  return useTodos(selectTodoCount)
}

进一步阅读

要深入了解这些主题,请阅读社区资源中的文章 React Query 渲染优化。要学习如何最好地优化 select 选项,请阅读 React Query 选择器,超级加强版

最后更新于

这有帮助吗?