You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Router 6中如何按查询参数条件使用加载器并复用数据?

解决方案:React Router 中查询参数场景下的数据缓存复用

针对你遇到的「提交搜索表单后重复触发 Suspense/Await 流程、重复请求数据」的问题,以下是几种成熟的处理模式:


方案 1:组件内缓存已加载数据(最轻量化)

核心思路是在组件内部用 useRef 持久化已获取的全量数据,URL 参数变化时直接复用缓存过滤,避免重新走 Suspense 流程。

调整后组件代码

import { useLoaderData, useSearchParams, Suspense, Await, useNavigate } from "react-router-dom";
import { useRef } from "react";

type Item = { id: string; content: string };

const ItemsComponent = () => {
  const { itemsAsync } = useLoaderData() as { itemsAsync: Promise<Item[]> };
  const [searchParams] = useSearchParams();
  const navigate = useNavigate();
  const cachedItems = useRef<Item[] | null>(null); // 持久化缓存数据
  const searchQuery = searchParams.get("search")?.toLowerCase() ?? "";

  // 数据过滤逻辑
  const filterItems = (items: Item[]) => 
    items.filter(item => item.content.toLowerCase().includes(searchQuery));

  // 手动处理表单提交,避免触发 Loader 重新执行
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const search = formData.get("search") as string;
    navigate(search ? `/items?search=${encodeURIComponent(search)}` : "/items");
  };

  // 已有缓存时直接渲染过滤结果
  if (cachedItems.current) {
    return (
      <div>
        <form onSubmit={handleSubmit}>
          <input 
            type="text" 
            name="search" 
            defaultValue={searchQuery}
            placeholder="输入搜索关键词"
          />
          <button type="submit">搜索</button>
        </form>
        {filterItems(cachedItems.current).map(item => (
          <div key={item.id}>{item.content}</div>
        ))}
      </div>
    );
  }

  // 无缓存时走 Suspense 加载流程,加载完成后存入缓存
  return (
    <Suspense fallback={<div>Loading items...</div>}>
      <Await resolve={itemsAsync}>
        {(items) => {
          cachedItems.current = items;
          return (
            <div>
              <form onSubmit={handleSubmit}>
                <input type="text" name="search" placeholder="输入搜索关键词" />
                <button type="submit">搜索</button>
              </form>
              {items.map(item => (
                <div key={item.id}>{item.content}</div>
              ))}
            </div>
          );
        }}
      </Await>
    </Suspense>
  );
};

export default ItemsComponent;

方案 2:Loader 层面全局缓存(跨组件共享)

如果需要在多个组件间复用全量数据,可以在 Loader 中实现全局内存缓存,根据搜索参数决定是否重新请求 API。

调整后 Loader 代码

import { LoaderFunction, defer } from "react-router-dom";
import { getItems } from "./api"; // 你的 API 请求函数

type Item = { id: string; content: string };
let globalItemsCache: Item[] | null = null; // 全局内存缓存

export const loader: LoaderFunction = async ({ request }) => {
  const url = new URL(request.url);
  const searchQuery = url.searchParams.get("search");

  // 无搜索参数 / 缓存为空时,重新请求数据并更新缓存
  if (!searchQuery || !globalItemsCache) {
    const items = await getItems();
    globalItemsCache = items;
    return defer({ itemsAsync: Promise.resolve(items) });
  }

  // 有搜索参数且缓存存在时,直接返回缓存数据
  return { items: globalItemsCache };
};

对应组件代码

import { useLoaderData, useSearchParams, Suspense, Await, useNavigate } from "react-router-dom";

type Item = { id: string; content: string };

const ItemsComponent = () => {
  const loaderData = useLoaderData() as { itemsAsync?: Promise<Item[]>; items?: Item[] };
  const [searchParams] = useSearchParams();
  const navigate = useNavigate();
  const searchQuery = searchParams.get("search")?.toLowerCase() ?? "";

  const filterItems = (items: Item[]) => 
    items.filter(item => item.content.toLowerCase().includes(searchQuery));

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const search = formData.get("search") as string;
    navigate(search ? `/items?search=${encodeURIComponent(search)}` : "/items");
  };

  // 直接使用缓存数据渲染
  if ("items" in loaderData) {
    return (
      <div>
        <form onSubmit={handleSubmit}>
          <input 
            type="text" 
            name="search" 
            defaultValue={searchQuery}
            placeholder="输入搜索关键词"
          />
          <button type="submit">搜索</button>
        </form>
        {filterItems(loaderData.items).map(item => (
          <div key={item.id}>{item.content}</div>
        ))}
      </div>
    );
  }

  // 首次加载走 Suspense 流程
  return (
    <Suspense fallback={<div>Loading items...</div>}>
      <Await resolve={loaderData.itemsAsync!}>
        {(items) => (
          <div>
            <form onSubmit={handleSubmit}>
              <input type="text" name="search" placeholder="输入搜索关键词" />
              <button type="submit">搜索</button>
            </form>
            {items.map(item => (
              <div key={item.id}>{item.content}</div>
            ))}
          </div>
        )}
      </Await>
    </Suspense>
  );
};

export default ItemsComponent;

方案 3:利用 React Router 的 shouldRevalidate 阻止 Loader 重跑

通过路由配置中的 shouldRevalidate 函数,仅当路径变化时才重新执行 Loader,搜索参数变化时跳过 Loader 调用,结合组件缓存实现复用。

路由配置调整

import { createBrowserRouter } from "react-router-dom";
import { ItemsComponent } from "./ItemsComponent";
import { loader } from "./loader";

export const router = createBrowserRouter([
  {
    path: "/items",
    element: <ItemsComponent />,
    loader,
    // 仅当路径名变化时,才重新执行 Loader
    shouldRevalidate({ currentUrl, nextUrl }) {
      return currentUrl.pathname !== nextUrl.pathname;
    },
  },
]);

组件代码同方案 1

此方案下,搜索参数变化不会触发 Loader 重新执行,组件直接复用缓存数据过滤,完全避免重复请求。


内容的提问来源于stack exchange,提问作者Nikolai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 07:40:59