跳到主要内容

 

预取

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

你可能想要做这个的情况有很多,但一些非常常见的用例是:

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

使用 React Hooks 进行预取

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

它接受两个参数:第一个是你在 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')

// 低优先级的悬停将不会触发,除非上次请求发生在35s前
// 高优先级的悬停将总是触发
return (
<div>
<button onMouseEnter={() => prefetchUser(4, { ifOlderThan: 35 })}>
低优先级
</button>
<button onMouseEnter={() => prefetchUser(4, { force: true })}>
高优先级
</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))
}, [])
}

// 在一个组件中
usePrefetchImmediately('getUser', 5)

不使用钩子的预取

如果你不使用 usePrefetch 钩子,你可以在任何框架中自己重现相同的行为。

当你像下面这样分派 prefetch thunk 时,你会看到与这里描述的完全相同的行为。

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

你也可以分派查询操作,但你需要负责实现任何额外的逻辑。

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

预取示例

基本预取

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

自动预取

在我们的最后一个例子中,我们自动 prefetch 下一页,给人一种没有网络延迟的感觉。

预取所有已知页面

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