news 2026/9/14 8:28:22

TanStack Router:retainSearchParams 搜索中间件——让 Search Params 在导航间自动保留

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TanStack Router:retainSearchParams 搜索中间件——让 Search Params 在导航间自动保留

TanStack Router:retainSearchParams 搜索中间件——让 Search Params 在导航间自动保留

【免费下载链接】router🤖 A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router

在 TanStack Router 中,每次客户端导航(link/navigate)都会按目标路由重新解析 search params,当前 URL 中的查询参数(如分页页码、筛选条件、会话标识)很容易被"丢掉"。retainSearchParams是一个开箱即用的search middleware:把它挂到路由的search.middlewares上后,指定的搜索参数(或全部参数)会在后续导航中自动保留,无需在每处to/search里手动透传。本文基于仓库中 retainSearchParamsFunction.md 的官方文档,结合 router-core 源码 与测试用例,完整讲解它的用法、参数语义和底层工作原理。

读完本文,你将能够:

  • 在 code-based(createRootRoute/createRoute)与 file-based(createFileRoute)路由上正确配置retainSearchParams
  • 理解它true与 key 数组两种入参的行为差异,以及"显式导航值优先"的保留规则;
  • 看懂SearchMiddleware类型、中间件执行管线与meta元数据在参数保留/剔除中的作用。

retainSearchParams 的入参:true 或 key 列表

retainSearchParams接受两种形式的keys

  • true:保留当前 search 中的所有参数;
  • Array<keyof TSearchSchema>:只保留列表里列出的 key(类型上被约束为当前路由 search schema 的 key)。

示例一:保留指定 key(根路由,code-based)

官方文档给出的第一个示例是在根路由上保留rootValue

import { z } from 'zod' import { createRootRoute, retainSearchParams } from '@tanstack/react-router' const searchSchema = z.object({ rootValue: z.string().optional(), }) export const Route = createRootRoute({ // Zod v4 可以直接传 schema validateSearch: searchSchema, search: { middlewares: [retainSearchParams(['rootValue'])], }, })

这里middlewaressearch配置项下的数组,retainSearchParams(['rootValue'])表示:后续任何导航中,只要目标 search 没有显式指定rootValue,就沿用当前 URL 里的rootValue

示例二:保留全部参数(file-based 路由)

第二个示例演示了true入参以及 file-based 路由写法:

import { z } from 'zod' import { createFileRoute, retainSearchParams } from '@tanstack/react-router' const searchSchema = z.object({ one: z.string().optional(), two: z.string().optional(), }) export const Route = createFileRoute('/')({ // Zod v4 可以直接传 schema validateSearch: searchSchema, search: { middlewares: [retainSearchParams(true)], }, })

true时,onetwo等全部参数在导航后都会保留(显式覆盖的除外)。

注意两点适用前提:

  1. validateSearch使用 Zod 时,文档注释明确提示"Zod v4 可以直接使用 schema",旧版 Zod 通常需要包一层zodValidator(searchSchema)(仓库文档中以 Zod v4 为准);
  2. retainSearchParams挂在哪个路由上,就在该路由参与的目标路由链路中生效。中间件会按目标路由链从外到内收集(见下文applySearchMiddleware源码分析),因此把"全局性"的保留逻辑放在根路由上是最常见的做法。

源码解析:retainSearchParams 的实现

retainSearchParams的核心实现在 searchMiddleware.ts(router-core包,React / Solid / Vue 三端均从router-core再导出,例如 react-router/src/index.tsx):

export function retainSearchParams<TSearchSchema extends object>( keys: Array<keyof TSearchSchema> | true, ): SearchMiddleware<TSearchSchema> { return ({ search, next }) => { const { search: resultSearch, meta } = ( next as unknown as SearchMiddlewareNextWithMeta<TSearchSchema> )(search, true) if (keys === true) { const copy = { ...search, ...resultSearch } // 1) 处理"被默认值填充/被移除"的 key:显式导航值或值未变化时删除, // 让保留逻辑尊重导航方显式给出的参数 // 2) meta.removedAny 中的 key 无条件删除 // 3) 被 validateSearch 用默认值填充的 key,如果当前 search 有真实值, // 则用当前值覆盖默认值 return copy } // keys 数组分支:以导航结果 resultSearch 为底, // 把列表中的 key 从当前 search 补回去(同样尊重显式值与默认值) const copy = { ...resultSearch } // ... 逐 key 补回逻辑 return copy } }

关键设计点:

  • 不可变(不 mutate):两个分支都先展开成新对象({ ...search, ...resultSearch }{ ...resultSearch }),从不修改传入的search。router-core 单元测试专门验证了这一点:retainSearchParams(['id', 'filter'])执行后原 search 对象保持不变,返回{ id: '1', filter: 'active', page: 'new' }
  • 显式导航值优先:源码中通过meta.explicit(本次导航显式声明的 search)与deepEqual比对判断——如果导航显式设置了某个参数(哪怕与当前值相同),就不会被"保留"覆盖;这保证了业务代码永远可以用显式传参打断保留行为。
  • 与 schema 默认值协同meta.defaulted记录哪些 key 是被validateSearch用默认值(如 Zod 的.default())填充的,retain 会用当前 URL 中的真实值替换这些默认值,避免"默认值把保留的参数顶掉"。

中间件管线:SearchMiddleware 类型与执行顺序

类型定义

中间件相关类型定义在 route.ts:

export type SearchMiddlewareMeta = { removed?: Map<string, unknown> // 被"按值"移除的 key 及其默认值 removedAny?: Set<string> // 被"无条件"移除的 key defaulted?: Map<string, unknown> // 被 schema 默认值填充的 key explicit?: unknown // 本次导航显式声明的 search 结果 } export type SearchMiddlewareContext<TSearchSchema> = { search: TSearchSchema // 当前 URL 的 search next: (newSearch: TSearchSchema) => TSearchSchema // 调用后续中间件 meta?: SearchMiddlewareMeta } export type SearchMiddleware<TSearchSchema> = ( ctx: SearchMiddlewareContext<TSearchSchema>, ) => TSearchSchema

这是一个经典的洋葱模型:search是当前 URL 的参数,next把(可能被修改过的)search 传给后续中间件,最终由路由配置的search函数式更新(dest.search)产生导航声明的目标参数。retainSearchParams内部调用next(search, true)(第二个参数collectMeta: true)拿到"后续管线算出的目标 search + 元数据",再在此基础上做保留——即它站在导航结果之前做后处理,只补回它负责的 key。

执行管线 applySearchMiddleware

管线装配与执行逻辑在 router.ts 的applySearchMiddleware中,导航构建目标 location 时调用(调用点见 router.ts#L2063):

  1. 收集中间件:遍历目标路由链destRoutes,依次push每个路由routeOptions.search.middlewares;旧的preSearchFilters/postSearchFilters会被包装成等价中间件兼容(源码注释标明将在 v2 移除);
  2. 追加 validate 中间件:当路由配置了validateSearch时,动态追加一个 validate 中间件——它先next(search)拿到导航结果,再执行 schema 校验/解析,并把"结果中出现但输入中没有的 key"记录到meta.defaulted(即被默认值填充的参数),最后返回{ ...result, ...validated }。这解释了上文"与 schema 默认值协同"的行为来源;
  3. 末端落地:中间件链耗尽后(index >= middlewares.length),若dest.search为函数/对象,则执行functionalUpdate(dest.search, currentSearch)得到最终声明值,并写入meta.explicit

从源码结构看,这意味着retainSearchParams拿到的resultSearch是"本次导航显式声明 + schema 校验后"的结果,而meta.explicit/meta.defaulted让它能区分"导航显式给的值"和"schema 默认值",从而实现精确的保留语义。

行为验证:官方测试用例

router-core 单元测试

searchMiddleware.test.ts 覆盖了三种关键行为:

  • 数组 key + next 不返回这些 keyretainSearchParams(['id', 'filter'])下,当前 search 为{ id: '1', filter: 'active', page: '2' }、导航结果为{ page: 'new' }时,最终得到{ id: '1', filter: 'active', page: 'new' }——保留项被补回,导航项正常更新;
  • true全量保留:当前 search 为{ id: '1', filter: 'active' }、导航结果为{ id: '2' }时,retainSearchParams(true)输出{ id: '2', filter: 'active' }——显式更新的id生效,未被提及的filter被保留;
  • 同引用复用安全:同一 search 对象多次调用中间件,结果一致且不产生变异。

三端集成测试(真实导航场景)

react-router 的集成测试 用真实路由(根路由/+/posts)验证:

  • 初始 search 为{ value: 'abc' },点击 link 导航到/posts后,router.state.location.search与 URL 中value=abc仍然存在(should retain value search param);
  • 初始 search 为空时,retainSearchParams(['value'])"什么都不做",导航后 search 为空(should do nothing if value search param is not set)。

Solid 与 Vue 端拥有同构的集成测试:solid-router/tests/searchMiddleware.test.tsx 与 vue-router/tests/searchMiddleware.test.tsx,说明该中间件行为在三个框架绑定层保持一致。

与 stripSearchParams 的对照

retainSearchParams同文件实现的 stripSearchParams 方向相反:它在导航时剔除参数(支持true全量剔除、key 数组、以及"值等于默认值才剔除"的对象形式),并通过meta.removed/meta.removedAny把剔除信息暴露给后续中间件——retainSearchParams正是读取这两个字段来决定"保留的 key 是否已被上游明确移除"。两者组合使用可以精细控制 URL 的"带什么、丢什么",其用法详见 stripSearchParamsFunction.md。

小结与适用要点

要点说明
挂载位置路由选项search.middlewares数组,根路由挂"全局保留",子路由挂"局部保留"
入参true(保留全部)或keyof TSearchSchema数组(保留指定 key),key 受 search schema 类型约束
显式优先导航中显式声明的参数会覆盖保留逻辑(由meta.explicit判定)
默认值协同被 schema 默认值填充的 key 会用当前 URL 真实值还原(由meta.defaulted判定)
不可变中间件不修改原 search 对象,测试中有专门断言
框架一致性实现在router-core,React / Solid / Vue 三端同构导出、各有集成测试

如果你希望在跨页导航中保持分页、筛选、来源追踪等 query 参数不丢失,retainSearchParams就是 TanStack Router 提供的官方手段:一行middlewares: [retainSearchParams([...])]即可声明,底层由 searchMiddleware.ts 与 router.ts 中的中间件管线 共同保证正确性。

【免费下载链接】router🤖 A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 8:28:15

AI短漫剧全链路云原生流水线实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 8:25:33

腾讯Agent Suite办公智能体套件:从编排到落地的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 8:23:29

SpringBoot+Vue构建智能废品回收系统实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 8:21:56

Tolaria 如何从 Portent 模板知识库起步并完成类型与关系初始化?

Tolaria 如何从 Portent 模板知识库起步并完成类型与关系初始化&#xff1f; 【免费下载链接】tolaria Desktop app to manage markdown knowledge bases 项目地址: https://gitcode.com/GitHub_Trending/to/tolaria 如果你的知识库是空文件夹&#xff0c;第一件事往往不…

作者头像 李华