渲染优化
React Query 会自动应用一些优化措施,以确保你的组件仅在真正需要时才重新渲染。这是通过以下方式实现的:
结构共享 (Structural Sharing)
React Query 使用一种称为“结构共享”的技术,以确保在重新渲染之间尽可能多地保持引用不变。如果数据是通过网络获取的,通常你会通过 JSON 解析响应得到一个全新的引用。然而,如果数据没有任何变化,React Query 将会保留原始引用。如果数据的某个子集发生了变化,React Query 将保留未更改的部分,仅替换已更改的部分。
注意:此优化仅在
queryFn返回与 JSON 兼容的数据时才有效。你可以通过全局或在单个查询上设置structuralSharing: false来关闭它,或者通过传入一个函数来实现你自己的结构共享逻辑。
引用标识 (Referential Identity)
从 useQuery、useInfiniteQuery、useMutation 返回的顶层对象,以及从 useQueries 返回的数组,都不是引用稳定的。这意味着每次渲染时它们都会是一个新的引用。然而,从这些 Hook 返回的 data 属性会尽可能保持稳定。
追踪的属性 (Tracked Properties)
React Query 只有在 useQuery 返回的某个属性真正被使用时,才会触发重新渲染。这是通过使用 Proxy 对象 实现的。这避免了许多不必要的重新渲染,例如,像 isFetching 或 isStale 这样的属性可能会频繁变化,但如果组件中没有使用它们,就不会触发重新渲染。
你可以通过手动设置 notifyOnChangeProps 来自定义此功能,可以在全局或单个查询上进行设置。如果你想完全关闭此功能,可以将 notifyOnChangeProps 设置为 'all'。
代理(Proxy)的 get 陷阱(trap)会在访问属性时被触发,无论是通过解构还是直接访问。如果你使用对象的剩余属性解构(object rest destructuring),你将禁用此优化。我们有一条 lint 规则 来防止这种陷阱。
select
你可以使用 select 选项来选择数据的一个子集,你的组件将只订阅这个子集。这对于高度优化的数据转换或避免不必要的重新渲染非常有用。
export const useTodos = (select) => {
return useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
select,
})
}
export const useTodoCount = () => {
return useTodos((data) => data.length)
}使用 useTodoCount 自定义 Hook 的组件,只有在待办事项列表的长度发生变化时才会重新渲染。如果某个待办事项的名称发生了变化,它不会重新渲染。
注意!
select 操作是在缓存中的成功数据上进行的,不是抛出错误的合适位置。错误的唯一来源是 queryFn。如果 select 函数返回一个错误,会导致 data 变为 undefined 且 isSuccess 变为 true。我们建议,如果你想让查询在数据错误时失败,请在 queryFn 中处理错误;或者,如果错误情况与缓存无关,则在查询 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 选择器,超级加强版
最后更新于
这有帮助吗?