TanStack Router ToOptions 类型详解:类型安全的导航目标描述与路由遮罩
2026/9/14 2:53:49 网站建设 项目流程

TanStack Router ToOptions 类型详解:类型安全的导航目标描述与路由遮罩

【免费下载链接】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(本仓库packages/*实现的客户端优先、全栈类型安全路由框架)中的ToOptions类型。ToOptions是描述"一次导航要去哪里、带什么参数、以什么方式呈现"的核心契约,useNavigateLinkmatchRoute以及redirect等 API 的参数类型都构建在它之上。读完本篇,你将掌握ToOptions各属性(from/to/hash/state/search/params/mask)的取值形态与类型收窄机制,并能基于源码理解其maskedLocation的构建逻辑。

ToOptions 是什么

官方 API 文档 ToOptionsType.md 对该类型的定义是:ToOptions包含若干用于描述一个路由目标的属性,其中包括用于路由遮罩(route masking)的mask。文档给出的类型形态如下:

type ToOptions = { from?: ValidRoutePath | string to?: ValidRoutePath | string hash?: true | string | ((prev?: string) => string) state?: true | HistoryState | ((prev: HistoryState) => HistoryState) } & SearchParamOptions & PathParamOptions & MaskOptions type SearchParamOptions = { search?: true | TToSearch | ((prev: TFromSearch) => TToSearch) } type PathParamOptions = { params?: | true | Record<string, TPathParam> | ((prev: TFromParams) => TToParams) } type MaskOptions = { mask?: ToMaskOptions<TRouter, TMaskFrom, TMaskTo> }

在仓库源码中,该类型定义于 link.ts,并携带 5 个泛型参数,这正是其"全量类型安全"的来源:

// packages/router-core/src/link.ts#L360-L366 export type ToOptions< TRouter extends AnyRouter = RegisteredRouter, TFrom extends string = string, TTo extends string | undefined = '.', TMaskFrom extends string = TFrom, TMaskTo extends string = '.', > = ToSubOptions<TRouter, TFrom, TTo> & MaskOptions<TRouter, TMaskFrom, TMaskTo>
  • TRouter:具体路由实例类型,决定路由树、各路由的allParamsfullSearchSchema
  • TFrom:导航出发路由路径字面量;
  • TTo:目标路径字面量('.'表示当前路由);
  • TMaskFrom/TMaskTo:遮罩路由的出发/目标路径,独立参与类型收窄。

文档中的ValidRoutePathTToSearch等是简化的泛型占位符,源码中分别由ToPathOption/FromPathOption等约束类型展开,下文逐属性展开。

属性总览

属性类型说明
fromRoutePaths<TRouter['routeTree']>出发路由;useNavigate场景下通常由getRouteApi自动提供,此处主要用于链接/跨路由导航
toToPathOption<TRouter, TFrom, TTo>目标路由路径,支持绝对路径与相对路径(如'../profile'),并带编辑器路径补全
hashtrue \| Updater<string>锚点;true表示保留当前 hash
statetrue \| NonNullableUpdater<ParsedHistoryState, HistoryState>history state;true表示保留当前 state
searchtrue/ 对象 /(prev) => 对象目标搜索参数,按目标路由的validateSearch校验
paramstrue/Record<string, TPathParam>/(prev) => 对象路径参数,按目标路由的allParams校验
maskToMaskOptions<TRouter, TMaskFrom, TMaskTo>路由遮罩:实际路由与展示路径分离
unsafeRelative'path'源码级逃生舱,仅 link.ts 中定义,文档未列出

from 与 to:路径约束与自动补全

tofrom的类型并非简单的string。源码 link.ts#L616-L632 给出:

export type ToPathOption< TRouter extends AnyRouter = AnyRouter, TFrom extends string = string, TTo extends string | undefined = string, > = ConstrainLiteral< TTo, RelativeToPathAutoComplete< TRouter, NoInfer<TFrom> extends string ? NoInfer<TFrom> : '', NoInfer<TTo> & string > > export type FromPathOption<TRouter extends AnyRouter, TFrom> = ConstrainLiteral< TFrom, RoutePaths<TRouter['routeTree']> >

从源码结构看,ToPathOption会在三种候选集之间做联合:绝对路径(RouteToPath,即整棵路由树的路径)、相对当前路由可达的路径(RelativeToCurrentPath)、相对父级可达的路径(RelativeToParentPath,处理'../x'形式)。ConstrainLiteral的作用是把泛型TTo约束回这些候选字面量上——写错路径会在编译期直接报错,编辑器中还能获得路径补全。

to是必选还是可选由MakeToRequired决定(link.ts#L421-L431):当TFrom无法推断(退化为string)时放宽为可选,避免在未绑定具体路由上下文的场景误报;能确定TFromTTo是具体字面量时则要求必填。

hash 与 state:三种取值形态

hashstate采用统一的"三态"设计,源码定义见 link.ts#L433-L442:

export type ToSubOptionsProps< TRouter extends AnyRouter = RegisteredRouter, TFrom extends RoutePaths<TRouter['routeTree']> | string = string, TTo extends string | undefined = '.', > = MakeToRequired<TRouter, TFrom, TTo> & { hash?: true | Updater<string> state?: true | NonNullableUpdater<ParsedHistoryState, HistoryState> from?: FromPathOption<TRouter, TFrom> & {} unsafeRelative?: 'path' }

其中Updater/NonNullableUpdater定义在 utils.ts#L87-L91,本质是TResult | ((prev: TPrevious) => TResult)联合。因此:

  • hash: true:导航后保留地址栏现有锚点;hash: '#section':直接覆盖;hash: (prev) => next:基于旧锚点函数式更新;
  • state: true:保留当前 history state;传对象则整体替换;传函数时,入参prev的类型是ParsedHistoryState(比HistoryState多携带路由内部元数据),返回值是普通HistoryState。这与文档中(prev: HistoryState) => HistoryState的简化写法语义一致,但源码的prev更精确。

配合NavigateOptionProps(见replaceresetScrollviewTransition等,link.ts#L301-L358),ToOptions构成完整的导航描述。

search 与 params:按需必填的类型收窄

searchparams的可选性不是固定的,而是由"目标路由是否要求这些参数"动态推导的。源码链路如下(link.ts#L541-L614):

export interface MakeOptionalSearchParams<...> { search?: true | (ParamsReducer<TRouter, 'SEARCH', TFrom, TTo> & {}) } export interface MakeRequiredSearchParams<...> { search: MakeRequiredParamsReducer<TRouter, 'SEARCH', TFrom, TTo> & {} } export type SearchParamOptions<TRouter, TFrom, TTo> = IsRequired<TRouter, 'SEARCH', TFrom, TTo> extends never ? MakeOptionalSearchParams<TRouter, TFrom, TTo> : MakeRequiredSearchParams<TRouter, TFrom, TTo>

IsRequired(link.ts#L590-L604)会把TFromTToResolveRelativePath解析成真实路径,再取目标路由的allParams/fullSearchSchemaInput做必填性判断。由此得到两种编译期行为:

  1. 目标路由有必填 search/paramssearch/params变成必填属性,缺省会直接报错(MakeRequiredParamsReducer还允许传true继承当前值,前提是当前参数已满足目标要求);
  2. 目标路由参数全部可选:两者退化为可选。

参数值本身支持三种形态,ParamsReducer定义见 link.ts#L444-L460:

  • true:保留出发路由上同名参数的当前值;
  • 字面量对象:全量替换,params: { id: '42' }
  • 更新函数:search: (prev) => ({ ...prev, tab: 'settings' }),函数入参是出发路由的完整参数/搜索类型,返回值按目标路由的输入 schema 校验。

一个可复制的典型用法(与上述类型完全吻合):

// 从 /user 导航到 /user/$id,保留当前 search,仅更新其中 tab router.navigate({ to: '/user/$id', params: { id: '42' }, search: (prev) => ({ ...prev, tab: 'settings' }), })

MaskOptions:路由遮罩(route masking)

maskToOptions中最有特色的部分。遮罩的含义是:实际渲染某条路由,但地址栏展示另一条路径——典型场景是把详情页以模态框形式呈现,分享链接时再"解除遮罩"回到真实 URL。

源码中MaskOptionsToMaskOptions定义在 link.ts#L368-L383:

export interface MaskOptions< in out TRouter extends AnyRouter, in out TMaskFrom extends string, in out TMaskTo extends string, > { _fromLocation?: ParsedLocation mask?: ToMaskOptions<TRouter, TMaskFrom, TMaskTo> } export type ToMaskOptions< TRouter extends AnyRouter = RegisteredRouter, TMaskFrom extends string = string, TMaskTo extends string = '.', > = ToSubOptions<TRouter, TMaskFrom, TMaskTo> & { unmaskOnReload?: boolean }

注意ToMaskOptions本身递归复用ToSubOptions——也就是说mask内部可以带自己的to/params/search,且这些参数按遮罩路由TMaskTo)的类型收窄,而非真实目标路由,这是TMaskFrom/TMaskTo两个独立泛型的意义。unmaskOnReload控制页面刷新后是否解除遮罩。

源码如何消费 mask

RouterCore构建 location 时(router.ts#L2146-L2175):

const next = build(opts) if (opts.mask) { next.maskedLocation = build({ from: opts.from, ...opts.mask, }) } else if (this.options.routeMasks) { const match = findFlatMatch<RouteMask<TRouteTree>>( next.pathname, this.processedTree, ) if (match) { const params = Object.assign(Object.create(null), match.rawParams) const { from: _from, params: maskParams, ...maskProps } = match.route const nextParams = resolveNextParams(maskParams, params) next.maskedLocation = build({ from: opts.from, ...maskProps, params: nextParams, }) } }

从源码结构看,遮罩解析有两条路径:

  1. 显式opts.mask:以同一套build逻辑基于mask.to额外构建一个maskedLocation,与真实 location 并存;
  2. 路由树级routeMasks:未显式传mask时,若构建后的真实路径命中了路由树中的某个RouteMask(由createRouteMask声明),则自动套用该 mask 的to/ 默认paramsparams也支持函数形式,会以真实路由匹配到的参数为上下文求值。

提交到 history 时,unmaskOnReload的取值优先级为:导航级选项 > 遮罩自身选项 > 路由级全局unmaskOnReload(见 router.ts#L2249-L2253 的??链)。后续读取 location 时,对外暴露的一律是location.maskedLocation ?? location(router.ts#L2354、#L2530),保证useLocation等 hook 拿到的始终是"用户可见"的地址。

完整的路由遮罩概念、模态框案例与刷新解除行为,可继续阅读 route-masking 指南 与 RouteMaskType.md、ToMaskOptionsType.md;仓库内 examples/react/location-masking 提供了一个可运行的遮罩示例应用。

ToOptions 在上层 API 中的复用

ToOptions是整个导航体系的公共底座,NavigateOptions直接在其上叠加行为开关(link.ts#L289-L295):

export type NavigateOptions< TRouter extends AnyRouter = RegisteredRouter, TFrom extends string = string, TTo extends string | undefined = '.', TMaskFrom extends string = TFrom, TMaskTo extends string = '.', > = ToOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> & NavigateOptionProps

由此形成的复用关系(均以源码为据):

  • useNavigate返回的navigate接受NavigateOptions(useNavigate.ts);
  • Linkto相关 props 是NavigateOptions & LinkOptionsProps(link.ts#L702-L704),因此<Link to mask search params>与编程式导航享受同一套类型收窄;
  • redirect()/createRedirect()同样以NavigateOptions为参数(redirect.ts#L13-L16),loader 内跳转时可用完全一致的写法;
  • matchRoute的路由位置参数直接就是ToOptions(router.ts#L723-L744),用于在组件外判断"某位置是否命中某路由并取出参数";
  • preloadRoute亦以NavigateOptions为参数(router.ts#L696-L721)。

行为类开关(replaceresetScrollviewTransitionignoreBlockerreloadDocumenthrefhashScrollIntoView)不属于ToOptions本体,而属于NavigateOptionPropsToOptions只负责"去哪里、带什么、以什么面貌呈现"。更多说明见 NavigateOptionsType.md 与 LinkOptionsType.md。

小结

  • ToOptions是 TanStack Router 中导航目标的类型契约:from/to决定去向且带路径补全,hash/state支持true、字面量、更新函数三态,search/params按目标路由 schema 动态决定必填性与校验,mask提供真实路由与展示路径分离的遮罩能力;
  • 其可选性/必填性由IsRequired+MakeRequired/Optional*条件类型在编译期推导(link.ts#L590-L614),把运行时参数错误前移到类型检查阶段;
  • mask的最终落地是maskedLocation的并行构建(router.ts#L2148-L2172),并可通过routeMasks在路由树上声明式复用;
  • 需要继续深入时,建议按 ToOptionsType.md → NavigateOptionsType.md → route-masking 指南 → examples/react/location-masking 的顺序阅读。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询