渲染优化
Preact Query 会自动应用若干优化措施,确保你的组件仅在真正需要时才重新渲染。具体通过以下方式实现:
结构共享(structural sharing)
Preact Query 使用一种称为“结构共享”的技术,以尽可能在多次渲染之间保持对象引用不变。如果数据是通过网络拉取的,通常你会因对响应进行 JSON 解析而获得一个全新的引用。但 Preact Query 会在数据 完全未发生变化 时保留原始引用;若仅部分数据变更,则仅替换变更的部分,其余部分仍复用原有引用。
注意:该优化仅在
queryFn返回符合 JSON 规范的数据时生效。你可通过全局设置structuralSharing: false,或为单个查询单独设置该选项来禁用它;你也可以传入自定义函数来实现自己的结构共享逻辑。
引用稳定性(referential identity)
useQuery、useInfiniteQuery、useMutation 返回的顶层对象,以及 useQueries 返回的数组,均不保证引用稳定——每次渲染都会生成新引用。但这些 Hook 返回的 data 属性会尽可能保持引用稳定。
跟踪属性(tracked properties)
Preact Query 仅在 useQuery 返回的某个属性被组件“实际使用”时,才触发重新渲染。该机制基于 Proxy 对象 实现。这可避免大量不必要的重新渲染,例如 isFetching 或 isStale 等属性可能频繁变化,但组件并未使用它们。
你可以通过手动设置 notifyOnChangeProps,在全局或单个查询层面定制该行为。若要彻底禁用此功能,可将 notifyOnChangeProps: 'all' 设为 true。
注意:Proxy 的 get 拦截器会在访问属性时触发,无论你是通过解构还是直接访问。若使用对象剩余属性解构(object rest destructuring),该优化将失效。我们提供了一条 lint 规则 来防范这一陷阱。
select
你可以使用 select 选项,指定组件需订阅的数据子集。这对高度优化的数据转换或避免不必要重新渲染非常有用。
1export const useTodos = (select) => {2 return useQuery({3 queryKey: ['todos'],4 queryFn: fetchTodos,5 select,6 })7}8 9export const useTodoCount = () => {10 return useTodos((data) => data.length)11}使用 useTodoCount 自定义 Hook 的组件,仅在 todos 数组长度变化时重新渲染;若某个 todo 的 name 发生变化,则 不会 触发重新渲染。
注意:
select作用于已成功缓存的数据,不适用于抛出错误。错误的唯一信源是queryFn;若select函数返回错误,则data将变为undefined,且isSuccess将变为true。如需让查询在数据异常时失败,请在queryFn中处理错误;若错误与缓存无关,则应在查询 Hook 外部处理。
记忆化(memoization)
select 函数仅在以下任一条件满足时重新执行:
select函数自身发生引用变化data发生变化
这意味着上例中内联定义的 select 函数将在每次渲染时执行。为避免此问题,你可将 select 函数包裹在 useCallback 中,或将其提取为一个无依赖的稳定函数引用:
1// wrapped in useCallback2export const useTodoCount = () => {3 return useTodos(useCallback((data) => data.length, []))4}1// extracted to a stable function reference2const selectTodoCount = (data) => data.length3 4export const useTodoCount = () => {5 return useTodos(selectTodoCount)6}延伸阅读
如需深入了解上述主题,请阅读 TkDodo 撰写的深度指南:Preact Query 渲染优化。如需掌握 select 选项的最佳实践,请参阅:Preact Query 选择器,全面增强版