Usage > Prefetching: fetching before user interaction">Usage > Prefetching: fetching before user interaction">
跳至主要内容

预取

预取的目标是在用户导航到页面或尝试加载一些已知内容 *之前* 获取数据。

有几种情况你可能想要这样做,但一些非常常见的用例是

  1. 用户将鼠标悬停在导航元素上
  2. 用户将鼠标悬停在作为链接的列表元素上
  3. 用户将鼠标悬停在下一个分页按钮上
  4. 用户导航到页面,并且你知道树中的一些组件将需要这些数据。这样,你可以防止获取瀑布。

使用 React Hooks 预取

类似于 useMutation 钩子,usePrefetch 钩子不会自动运行 - 它返回一个“触发函数”,可用于启动行为。

它接受两个参数:第一个是您在 API 服务中定义的查询操作的键 定义在您的 API 服务中,第二个是包含两个可选参数的对象。

usePrefetch 签名
export type PrefetchOptions =
| { force?: boolean }
| {
ifOlderThan?: false | number;
};

usePrefetch<EndpointName extends QueryKeys<Definitions>>(
endpointName: EndpointName,
options?: PrefetchOptions
): (arg: QueryArgFrom<Definitions[EndpointName]>, options?: PrefetchOptions) => void;

自定义钩子行为

您可以在声明钩子或在调用点指定这些预取选项。调用点将优先于默认值。

  1. ifOlderThan - (默认:false | number) - 数字以秒为单位
    • 如果指定,它将仅在 new Date() 和上次 fulfilledTimeStamp 之间的差值大于给定值时运行查询。
  2. force
    • 如果 force: true,它将忽略 ifOlderThan 值(如果设置),即使查询存在于缓存中,也会运行查询。

触发函数行为

  1. 触发函数始终返回 void
  2. 如果在声明或在调用点设置了 force: true,则无论如何都会运行查询。唯一例外是如果相同的查询已经在进行中。
  3. 如果没有指定选项并且查询存在于缓存中,则不会执行查询。
  4. 如果没有指定选项并且查询不存在于缓存中,则将执行查询。
    • 假设您在树中有一个 useQuery 钩子,它订阅了您正在预取的相同查询。
      • useQuery 将返回 {isLoading: true, isFetching: true, ...rest}
  5. 如果指定了 ifOlderThan 但评估结果为 false 并且查询在缓存中,则不会执行查询。
  6. 如果指定了 ifOlderThan 并且评估结果为 true,即使存在现有缓存条目,也会执行查询。
    • 假设您在树中有一个 useQuery 钩子,它订阅了您正在预取的相同查询。
      • useQuery 将返回 {isLoading: false, isFetching: true, ...rest}
usePrefetch 示例
function User() {
const prefetchUser = usePrefetch('getUser')

// Low priority hover will not fire unless the last request happened more than 35s ago
// High priority hover will _always_ fire
return (
<div>
<button onMouseEnter={() => prefetchUser(4, { ifOlderThan: 35 })}>
Low priority
</button>
<button onMouseEnter={() => prefetchUser(4, { force: true })}>
High priority
</button>
</div>
)
}

食谱:立即预取

在某些情况下,您可能希望立即预取资源。您可以在几行代码中实现这一点。

hooks/usePrefetchImmediately.ts
type EndpointNames = keyof typeof api.endpoints

export function usePrefetchImmediately<T extends EndpointNames>(
endpoint: T,
arg: Parameters<(typeof api.endpoints)[T]['initiate']>[0],
options: PrefetchOptions = {},
) {
const dispatch = useAppDispatch()
useEffect(() => {
dispatch(api.util.prefetch(endpoint, arg as any, options))
}, [])
}

// In a component
usePrefetchImmediately('getUser', 5)

无需 Hook 的预取

如果您没有使用 usePrefetch hook,您可以在任何框架中自行重现相同的行为。

当您像下面这样调度 prefetch thunk 时,您将看到与此处描述的完全相同的行为。

非 Hook 预取示例
store.dispatch(
api.util.prefetch(endpointName, arg, { force: false, ifOlderThan: 10 }),
)

您也可以调度查询操作,但您需要负责实现任何额外的逻辑。

手动预取的另一种方法
dispatch(api.endpoints[endpointName].initiate(arg, { forceRefetch: true }))

预取示例

基本预取

这是一个非常基本的示例,展示了如何在用户将鼠标悬停在下一个箭头时进行预取。这可能不是最佳解决方案,因为如果他们将鼠标悬停、点击,然后在不移动鼠标的情况下更改页面,我们不会知道要预取下一页,因为我们不会看到下一个 onMouseEnter 事件。在这种情况下,您需要自行处理。您也可以考虑自动预取下一页...

自动预取

从我们上一个示例开始,我们自动 prefetch 下一页,给人一种没有网络延迟的感觉。

预取所有已知页面

useQuery 初始化的第一个查询运行后,我们自动获取所有剩余页面。