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
相关产品推荐
相关产品推荐

