如何在 React Router Loaders 中使用 React Query(预取和缓存数据)

发布: (2026年2月26日 GMT+8 20:11)
5 分钟阅读
原文: Dev.to

Source: Dev.to

问题

当你导航到一个页面时,通常会有数据获取的延迟。用户会看到加载指示器,内容在请求完成后才出现。体验并不理想。

如果数据在页面加载时已经准备好怎么办?
React QueryReact Router loaders 结合即可实现这一点。

简明说明

  • Loader 在组件挂载 之前 运行(React Router 在导航时调用它)。

  • 在 Loader 中,我们询问 React Query:“你已经缓存了这些数据吗?”

    • → 立即使用。无需网络请求。
    • → 立即获取,等待完成,然后缓存。

当组件最终挂载时,它会使用相同的查询调用 useQuery。由于数据已经缓存,组件会 立即 渲染——没有加载状态。

关键方法是 queryClient.ensureQueryData(queryOptions)。可以把它理解为:“确保该数据存在——从缓存获取或去获取。”

步骤示例:一个简单的宝可梦页面

1. 设置查询

// pages/Pokemon.jsx
import { useQuery } from '@tanstack/react-query';
import { useLoaderData } from 'react-router-dom';
import axios from 'axios';

// A function that returns the query config (key + fetch function).
// We reuse this in BOTH the loader and the component.
const pokemonQuery = (name) => ({
  queryKey: ['pokemon', name],
  queryFn: async () => {
    const response = await axios.get(
      `https://pokeapi.co/api/v2/pokemon/${name}`
    );
    return response.data;
  },
});

2. 创建 Loader

// The loader receives queryClient from the router setup (see step 4).
// It runs BEFORE the component mounts.
export const loader = (queryClient) => {
  return async ({ params }) => {
    const { name } = params;

    // ensureQueryData checks the cache first:
    //   - cached? → returns it instantly
    //   - not cached? → fetches, caches, and returns it
    await queryClient.ensureQueryData(pokemonQuery(name));

    // We only return the param — the actual data lives in React Query's cache
    return { name };
  };
};

3. 构建组件

const Pokemon = () => {
  // Get the param that the loader returned
  const { name } = useLoaderData();

  // useQuery uses the SAME query config as the loader.
  // Since ensureQueryData already cached it, this renders instantly.
  const { data: pokemon } = useQuery(pokemonQuery(name));

  return (
    <>
      <h2>{pokemon.name}</h2>
      <p>Height: {pokemon.height}</p>
      <p>Weight: {pokemon.weight}</p>
    </>
  );
};

export default Pokemon;

4. 在路由中连接

// App.jsx
import {
  createBrowserRouter,
  RouterProvider,
} from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

import Pokemon, { loader as pokemonLoader } from './pages/Pokemon';

const queryClient = new QueryClient();

const router = createBrowserRouter([
  {
    path: '/pokemon/:name',
    element: <Pokemon />,
    // Pass queryClient into the loader
    loader: pokemonLoader(queryClient),
  },
]);

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <RouterProvider router={router} />
    </QueryClientProvider>
  );
}

export default App;

整体流程

User clicks link to /pokemon/pikachu


Router calls loader BEFORE mounting the component


loader calls: await queryClient.ensureQueryData(pokemonQuery("pikachu"))

        ├── Cache HIT?  → Returns cached data instantly (no fetch)
        └── Cache MISS? → Fetches from API, caches result, then returns


Component mounts → useQuery(pokemonQuery("pikachu"))


Data is already in cache → Renders IMMEDIATELY (no loading spinner)

为什么不只使用 useQuery

方法会发生什么
useQuery only组件挂载 → 开始获取 → 显示加载中 → 显示数据
ensureQueryData in loader + useQuery在挂载前获取数据 → 组件立即渲染并显示数据

使用 loader 方法可以提供更流畅、更快的用户体验,尤其是在页面导航时。

关键要点

  • ensureQueryData = “如果已缓存,则使用缓存;如果未缓存,则获取并缓存。”
  • 创建一个 共享查询配置函数(例如 pokemonQuery),并在 loader 和组件中都使用它。
  • loader 预先填充缓存,使组件中的 useQuery 能立即找到数据。
  • 从 loader 只返回 params —— 不返回数据本身。数据存放在 React Query 的缓存中。

你的页面现在在导航时会瞬间加载,React Query 会免费处理缓存、后台重新获取以及陈旧数据。

0 浏览
Back to Blog

相关文章

阅读更多 »

三层响应式电子商务页眉

封面图片(Triple-Tier Responsive E-commerce Header) https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2...