TanStack Router createRoute 函数详解以代码方式构建全类型安全的路由树【免费下载链接】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/routercreateRoute是 TanStack Router本仓库核心包tanstack/react-router/tanstack/vue-router/tanstack/solid-router共用的核心 API中用于**代码式路由code-based routing**的工厂函数它接收一个RouteOptions配置对象返回一个Route实例多个Route实例再通过父路由的.addChildren组合成路由树最终交给createRouter创建路由器。读完本文你将掌握createRoute的完整选项语义、返回实例上的预绑定 Hook 与实用方法并能从仓库源码层面理解它的类型推导机制与运行时实现从而在需要精细控制路由结构而非文件约定式路由的场景下正确使用它。一、createRoute 的定位与返回值官方文档 createRouteFunction.md 对它的定义非常简洁createRoutefunction returns aRouteinstance. A route instance can then be passed to a root routes children to create a route tree, which is then passed to the router.即三要素要素说明参数options类型RouteOptions必填用于配置该路由实例返回值一个新的Route实例用途非根路由的创建入口实例挂到根路由children上形成路由树再传入createRouter({ routeTree })源码中的工厂函数实现在 React 包的 route.tsx 中可以看到createRoute的完整签名。它的 JSDoc 明确了使用边界/** * Creates a non-root Route instance for code-based routing. * * Use this to define a route that will be composed into a route tree * (typically via a parent routes addChildren). If youre using file-based * routing, prefer createFileRoute. */ export function createRoute TRegister unknown, TParentRoute extends RouteConstraints[TParentRoute] AnyRoute, TPath extends RouteConstraints[TPath] /, TFullPath extends RouteConstraints[TFullPath] ResolveFullPathTParentRoute, TPath, TCustomId extends RouteConstraints[TCustomId] string, TId extends RouteConstraints[TId] ResolveIdTParentRoute, TCustomId, TPath, TSearchValidator undefined, TParams ResolveParamsTPath, // ... 其余上下文/加载器相关类型参数 (options: RouteOptions...): Route... { return new Route...(options as any) }从源码结构看createRoute本身只是一个类型友好的工厂封装运行时逻辑只有一行——new Route(options)。真正的“魔法”在类型参数上TParentRoute由getParentRoute推断、TPath、TFullPath经ResolveFullPath由父路由拼接推导、TId经ResolveId推导、TParams经ResolveParamsTPath从路径字面量解析全部从options的入参中被精确推断并传递到返回的Route...泛型中同文件中的Route类构造函数route.tsx已标注deprecated Use the createRoute function instead官方推荐一律通过createRoute而非new Route(...)创建路由以获得完整的类型推断框架无关的核心逻辑下沉在 router-core 的 BaseRouteBaseRoute保存options并通过init()计算parentRoute、id、path、fullPath、to等内部属性均为 getter 暴露private字段以_前缀命名并持有children、rank、lazyFn懒加载/代码分割入口等状态。注意createRoute用于创建非根路由。根路由请使用 createRootRoute或需要强制 router context 时的 createRootRouteWithContext。二、RouteOptions 配置项全解createRoute唯一的入参options是 RouteOptions 类型它是整个 API 中信息量最大的部分。以下按功能分组完整梳理其配置项均继承自官方文档 RouteOptionsType.md2.1 路由标识getParentRoute/path/id配置项类型必填性说明getParentRoute() TParentRoute必填返回父路由的函数。这是全类型安全的前提——createRoute靠它推断TParentRoute从而推导TFullPath、TId并确保路由树被正确组装pathstring必填除非提供了id用于匹配的路径片段相对父路由idstring无path时必填为**无路径布局路由pathless layout route**指定唯一 id此类路由不参与 pathname 匹配其子路由会被“扁平化”到父路由进行匹配2.2 渲染组件配置项类型默认值说明componentRouteComponent/ 懒加载组件Outlet /路由匹配时渲染的内容errorComponent同上routerOptions.defaultErrorComponent路由进入错误状态时渲染的内容pendingComponent同上routerOptions.defaultPendingComponent路由处于 pending 且达到pendingMs阈值时显示notFoundComponentNotFoundRouteComponentrouterOptions.defaultNotFoundComponent路由未找到not found时渲染的内容React 包通过模块增强UpdatableRouteOptionsExtensions见 route.tsx为这些选项补充了框架特有类型例如根路由额外支持shellComponent在 SSR 流式渲染中包裹html外壳的组件。2.3 搜索参数校验validateSearch/search.middlewaresvalidateSearch(rawSearchParams: unknown) TSearchSchema。路由匹配时被调用返回的已校验的search params 会成为该路由的 search 类型并沿路由树向下推导。若抛错路由进入错误状态。注意它是规划回调planning callback对相同输入必须确定且无副作用不得在其中导航或修改应用/路由状态。可选地用SearchSchemaInput标签把参数类型标记为(searchParams: TSearchSchemaInput SearchSchemaInput) TSearchSchema此时Link /与navigate()的search参数将用TSearchSchemaInput而非TSearchSchema来约束便于表达“可选的 search 参数”等输入/输出类型不对称的场景。search.middlewares(({search, next}) TSearchSchema)[]。在生成指向该路由及其后代的新链接时中间件链式转换 search 参数第一个中间件接收当前 search后续中间件由上一个调用next触发。2.4 路径参数解析params.*及弃用的parseParams/stringifyParams配置项类型说明params.parse(rawParams: Recordstring, string) TParams \| false匹配时用原始 path params 构造类型化 params抛错则路由进入错误状态。同样是规划回调。实验性匹配入站路由时返回false可跳过本路由、继续尝试其他候选路由params.prioritynumber默认0当多个带params.parse的候选路由能匹配同一段 URL 时数值大的先尝试其params.parse返回false则继续尝试下一个候选。只影响使用params.parse的竞争候选静态路由仍优先于动态/可选/通配路由params.stringify(params: TParams) Recordstring, string用已解析的 params 构造 location生成链接时调用返回合法的字符串映射parseParams/stringifyParams⚠️ 已弃用请使用params.parse/params.stringify替代stringifyParams在提供parseParams时必填在 router-core 的类型定义 中可以看到ParamsOptions把params组织为{ parse?, priority?, stringify? }的嵌套对象并保留了deprecated的旧顶层字段以维持向后兼容。2.5 数据获取beforeLoad/loader/loaderDepsbeforeLoad可选在路由加载前运行失败则该路由的 loader 及后代都不会运行type beforeLoad ( opts: RouteMatch { search: TFullSearchSchema abortController: AbortController preload: boolean params: TAllParams context: TParentContext location: ParsedLocation navigate: NavigateFnAnyRoute // deprecated buildLocation: BuildLocationFnAnyRoute cause: preload | enter | stay }, ) PromiseTRouteContext | TRouteContext | void返回 Promise 时路由进入 pending 状态并挂起渲染达到pendingMs阈值后显示pendingComponentPromise 拒绝则路由进入错误状态返回的TRouteContext对象会合并进路由上下文可在loader及相关组件/方法中使用常见用途是鉴权检查未登录时throw redirect({ to: /login })或返回redirect对象opts.navigate已弃用将在下一个大版本移除统一改用throw redirect(...)见 redirectFunction.md导航期间普通错误成为该 match 的错误状态并交给onError预加载期间的普通错误与 not-found 结果则体现在返回的投机 match lane 中而不会让preloadRoute的 Promise 被拒绝。loader可选路由匹配时运行type loaderFn ( opts: RouteMatch { abortController: AbortController cause: preload | enter | stay context: TAllContext deps: TLoaderDeps location: ParsedLocation params: TAllParams preload: boolean parentMatchPromise: PromiseMakeRouteMatchFromRouteTParentRoute navigate: NavigateFnAnyRoute // deprecated route: AnyRoute }, ) PromiseTLoaderData | TLoaderData | void type loader loaderFn | { handler: loaderFn staleReloadMode?: background | blocking }返回的TLoaderData存于路由 match即使 match 失效后仍可保存在内存缓存中导航产生的数据按gcTime保留预加载产生的数据按preloadGcTime保留在渲染出下一个Outlet /之前路由 match 子树内的任意组件都可以用useLoaderData读取loaderDeps中返回的依赖必须出现在deps里才会生效对象形式{ handler, staleReloadMode }可配置加载器专属行为background保持 stale-while-revalidate陈旧数据先行、后台刷新blocking则等待陈旧匹配的重载完成后再继续。loaderDeps(opts: { search: TFullSearchSchema }) Recordstring, any。匹配前调用为路由匹配提供额外的唯一标识并作为“何时该重新加载”的依赖追踪器应返回可序列化、能跨导航唯一标识该匹配的值。它是规划回调兼缓存键函数必须确定且无副作用返回值上的toJSON等序列化方法同理。路径参数本身已用于唯一标识匹配无需重复返回但若匹配依赖 search 参数来区分则必须在此返回。2.6 缓存、预加载与重挂配置项类型 / 默认值说明staleTimenumber默认routerOptions.defaultStaleTime默认0loader 数据视为新鲜的毫秒时长期间再次匹配同一路由不会重新加载preloadStaleTimenumber默认routerOptions.defaultPreloadStaleTime默认30_000预加载产生的 loader 数据视为新鲜的时长另一预加载或首次导航可在此间隔内复用导航接受该代数据后的新鲜度判断改用staleTimegcTimenumber默认routerOptions.defaultGcTime5 分钟普通加载产生的未使用 loader 数据的保留窗口超期后在后续缓存对账时可被清理preloadGcTimenumber默认routerOptions.defaultPreloadGcTime5 分钟预加载产生的未使用数据的保留窗口是否“新鲜到可复用”由preloadStaleTime决定preloadboolean默认true为false时投机性预加载仍会执行beforeLoad但跳过loader真正的导航则两者都正常执行shouldReloadboolean \| ((args: LoaderArgs) boolean)false或返回false后续匹配不重载 loader 数据true或返回true后续匹配重载undefined或返回undefined遵循默认 stale-while-revalidate 行为remountDeps(opts: RemountDepsOptions) any决定导航后路由组件是否重挂载返回值与上次不同则重挂载返回值需 JSON 可序列化。默认导航后仍活跃的路由组件不会重挂载。示例remountDeps: ({ params }) params可在params变化时强制重挂载2.7 匹配行为与其他渲染选项caseSensitiveboolean为true时该路由按大小写敏感方式匹配wrapInSuspenseboolean为true时无条件将该路由强制包裹在 suspense 边界中而不管从检查其组件是否“有理由”包裹pendingMs默认routerOptions.defaultPendingMs1000路由 pending 多少毫秒后才显示pendingComponentpendingMinMs默认routerOptions.defaultPendingMinMs500pendingComponent一旦出现后至少显示的毫秒数防止其在屏幕上闪现一帧preSearchFilters/postSearchFilters⚠️ 已弃用请使用search.middlewares替代。前者在navigate/Link传入的用户函数之前被调用后者在其之后被调用都作用于生成本路由或其后代新链接时的 search 参数。2.8 生命周期与错误回调回调签名触发时机onError(error: any) void导航或预加载过程中抛出错误时调用若它抛redirect则重定向取代原始错误成为当前导航/预加载的控制流若抛 not-found 结果则取代当前 match lane 中的原始错误onEnter(match: RouteMatch) void路由从“上一个位置未匹配”变为“匹配且已加载”时位于 error/not-found 边界之下的路由不会进入活跃生命周期边界本身保持活跃曾被隐藏的路由在导航使其重新活跃时会收到onEnteronStay(match: RouteMatch) void路由在上一个位置已匹配且本位置仍匹配并加载完成时两次导航都必须让该路由处于第一个 error/not-found 边界以内的活跃分支上onLeave(match: RouteMatch) void路由从“上一个位置已匹配”变为“不再匹配”时当导航把一个原本活跃的路由隐藏到 error/not-found 边界之下时即使结构上仍匹配也会触发后台重载background reload只更新数据不派发这些生命周期回调onCatchReact/Vue 为(error: unknown) voidSolid 为(error: Error) void路由捕获到错误时调用默认取routerOptions.defaultOnCatch2.9 SSR 相关headers/head/scripts/codeSplitGroupingstype headers (opts: { matches: ArrayRouteMatch match: RouteMatch params: TAllParams loaderData?: TLoaderData }) PromiseRecordstring, string | Recordstring, string type head (ctx: { matches: ArrayRouteMatch match: RouteMatch params: TAllParams loaderData?: TLoaderData }) Promise{ links?: RouteMatch[links] scripts?: RouteMatch[headScripts] meta?: RouteMatch[meta] styles?: RouteMatch[styles] } | { links?: RouteMatch[links] scripts?: RouteMatch[headScripts] meta?: RouteMatch[meta] styles?: RouteMatch[styles] }headersSSR 渲染本路由时可返回自定义 HTTP 响应头name/value 键值对head返回注入到文档head的元素用于路由级 SEO 元数据、preload 链接、内联样式或自定义脚本scriptshead的简写只返回script元素等价于返回head的scripts字段codeSplitGroupingsArrayArrayloader | component | pendingComponent | notFoundComponent | errorComponent精细控制代码分割时把路由的哪些懒加载片段打进同一个 chunk每个内层数组即一个同 bundle 的资源组。三、返回的 Route 实例方法体系createRoute返回的Route实例其类型详见 RouteType.md提供以下方法3.1 树结构方法.addChildren(children: Route[])向该路由添加子路由并返回更新后的自身返回类型会反映新的 children 类型。这是“路由实例 → 路由树”的关键一步把createRoute的产物挂到父路由上.update(options: PartialUpdatableRouteOptions)创建后用新的部分选项原地更新路由实例并返回自身类型同步更新。在某些场景下创建后再更新选项可以打破循环类型引用.lazy(lazyImporter: () PromisePartialUpdatableRouteOptions)挂接一个懒加载导入器在路由加载时才解析用于代码分割对应BaseRoute.lazyFn字段见 route.ts.redirect(opts?: RedirectOptions)redirect函数的类型安全版本from自动预绑定为该路由的fullPath从而支持类型安全的相对重定向。例如import { createFileRoute } from tanstack/react-router export const Route createFileRoute(/dashboard/settings)({ beforeLoad: ({ context }) { if (!context.user) { // from 自动为 /dashboard/settings throw Route.redirect({ to: ../login, // 指向兄弟路由的相对路径 }) } }, })3.2 预绑定到路由的 React 便捷 API在 route.tsx 中框架层Route类在BaseRoute之上额外挂了一组预绑定到本路由的 Hook 与组件无需手写from参数成员等价调用Route.useMatch(opts?)useMatch({ ...opts, from: this.id })Route.useRouteContext(opts?)useRouteContext({ ...opts, from: this.id })Route.useSearch(opts?)useSearch({ ...opts, from: this.id })Route.useParams(opts?)useParams({ ...opts, from: this.id })Route.useLoaderDeps(opts?)useLoaderDeps({ ...opts, from: this.id })Route.useLoaderData(opts?)useLoaderData({ ...opts, from: this.id })Route.useNavigate()useNavigate({ from: this.fullPath })Route.Link预绑定from{this.fullPath}的Link组件React.forwardRef透传 ref这与 RouteType.md 中“...RouteApimethods: All of the methods fromRouteApiare available”的说明一致。当你在无法直接导入路由对象的文件中例如被代码分割切走的组件仍需要某路由的类型安全 API 时可以使用 getRouteApi 按路由 id 字符串获取一个等价绑定的RouteApi实例route.tsx。四、完整示例从 createRoute 到可运行的路由树文档给出的最小示例原样继承自 createRouteFunction.mdimport { createRoute } from tanstack/react-router import { rootRoute } from ./__root const Route createRoute({ getParentRoute: () rootRoute, path: /, loader: () { return Hello World }, component: IndexComponent, }) function IndexComponent() { const data Route.useLoaderData() return div{data}/div }注意其中的Route.useLoaderData()因为useLoaderData已被预绑定到Route.useLoaderData上组件内不需要也无法再传from参数且返回数据的类型由loader的返回值自动推导。把它扩展为完整的“创建 → 组树 → 创建路由 → 渲染”流程组树写法与仓库测试 Matches.test.tsx 中的路由搭建方式一致// __root.tsx import { createRootRoute } from tanstack/react-router import { Outlet } from tanstack/react-router export const rootRoute createRootRoute({ component: () Outlet /, }) // routes/invoices.tsx import { createRoute } from tanstack/react-router import { rootRoute } from ./__root import { Outlet } from tanstack/react-router const invoicesRoute createRoute({ getParentRoute: () rootRoute, path: invoices, loader: () [{ id: 1 }, { id: 2 }], // loader 返回类型会被推导进下游 component: () Outlet /, // 作为布局路由 }) // 组树addChildren 返回带 children 类型的新根路由实例 const routeTree rootRoute.addChildren([indexRoute, invoicesRoute]) // 创建路由并渲染 import { createRouter, RouterProvider } from tanstack/react-router const router createRouter({ routeTree }) // main.tsx createRoot(document.getElementById(root)!).render( RouterProvider router{router} /, )要点回顾getParentRoute: () rootRoute是类型安全的枢纽——TFullPath、TId、上下文与 params 的推导全部依赖它addChildren返回的是更新了 children 类型的父路由实例因此createRouter({ routeTree })能拿到一棵完整可推导的路由树createRootRoute的 JSDoc 也明确“Typically paired withcreateRouter({ routeTree })”见 route.tsx无路径布局路由仅id无path与嵌套参数路由的组合方式在 Matches.test-d.tsx 中有对应的类型级验证用例layoutRoute与invoicesRoute的嵌套场景。五、createRoute 与文件式路由的关系createRoute的 JSDoc 明确写道“If youre using file-based routing, prefercreateFileRoute。” 从仓库实现看createFileRoute 是虚拟模块由 router-generator 生成、经 virtual-file-routes 暴露注入的路径字面量版本的同类工厂路径已硬编码因此不需要getParentRoute其余RouteOptionscomponent、loader、beforeLoad等语义与createRoute完全一致。类似地createLazyRoute 提供“先占位、后Route.lazy(...)补全选项”的代码分割写法。简言之文件式路由createFileRoute及其 lazy 变体——路径来自文件名零配置适合绝大多数项目代码式路由createRoutegetParentRouteaddChildren——路径、id、层级完全由代码控制适合动态路由结构、路由工厂或不需要构建插件的场景。两条路径最终汇聚到同一个Route/BaseRoute运行时router-core/src/route.ts与同一套RouteOptions类型体系。六、小结与延伸阅读createRoute(options: RouteOptions)是 TanStack Router 代码式路由的入口必填getParentRoutepath与无路径id二选一返回可直接.addChildren/.update/.lazy的Route实例数据流由beforeLoad上下文/鉴权→loader数据→loaderDeps缓存键构成配合staleTime/gcTime/preload/shouldReload形成完整的缓存与预加载控制面路由实例自带预绑定 HooksuseLoaderData、useParams、useNavigate、Link等与类型安全相对重定向Route.redirect在无法导入路由对象的代码分割文件里可用getRouteApi等价替代。可进一步阅读RouteOptionsType.md全部配置项原文、RouteType.mdRoute类型完整方法、RouteApiType.md、createFileRouteFunction.md、createRouterFunction.md源码实现参考 packages/react-router/src/route.tsx 与 packages/router-core/src/route.ts测试参考 packages/react-router/tests/Matches.test.tsx。【免费下载链接】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),仅供参考