ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

TanStack Start 路由实战指南:文件式路由、根路由与类型安全路由树全解析

TanStack Start 路由实战指南:文件式路由、根路由与类型安全路由树全解析 TanStack Start 路由实战指南文件式路由、根路由与类型安全路由树全解析【免费下载链接】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/routerTanStack Start 构建在 TanStack Router 之上因此你在 Start 中配置的路由体系本质上就是一套基于文件的、完全类型安全的客户端路由。本文基于当前仓库 docs/start/framework/react/guide/routing.md 展开聚焦 Start 项目中的核心路由机制router.tsx的创建、src/routes目录的文件式路由、根路由与HeadContent/Outlet/Scripts三个关键组件、routeTree.gen.ts的类型安全生成机制以及嵌套路由与各类路由Index、Dynamic、Wildcard、Pathless Layout、Non-Nested、Grouped的完整使用方式。读完本文你将能够从零搭建一个多层级、类型安全、支持 SSR 文档壳的 TanStack Start 路由应用并能结合源码理解每个配置项背后的工作原理。The RouterStart 中的路由入口router.tsx的角色与位置在 TanStack Start 项目中src/router.tsx是定义路由行为的核心文件它决定了 Start 内部使用的 TanStack Router 实例如何创建与工作src/ ├── router.tsx在这个文件中你可以配置从默认的预加载preloading功能到数据加载缓存过期策略caching staleness在内的一切路由行为。router.tsx中导出的 Router 实例会被 Start 的服务端与客户端共享用于执行matchRoutes匹配路由、处理数据加载、管理导航状态等核心流程。为什么必须导出getRouter函数Start 要求router.tsx必须导出一个名为getRouter的函数该函数在每次被调用时都返回一个全新的 Router 实例而不是导出一个单例// src/router.tsx import { createRouter } from tanstack/react-router import { routeTree } from ./routeTree.gen // You must export a getRouter function that // returns a new router instance each time export function getRouter() { const router createRouter({ routeTree, scrollRestoration: true, }) return router }这样设计的原因在于Start 在服务端渲染SSR时每个请求都需要一个独立的 Router 实例来承载各自的请求上下文URL、加载状态、内存中的缓存等避免跨请求的状态污染而在客户端水合时则只创建一个实例。从源码看createRouter的 React 实现定义在 packages/react-router/src/router.ts它返回一个继承自RouterCore的Router类实例RouterCore则位于 packages/router-core/src/router.ts负责路由匹配、状态管理与数据加载的核心逻辑。createRouter常用配置说明上例中出现的routeTree与scrollRestoration只是众多可配置项中的两个其他常用选项包括完整定义见 packages/router-core/src/config.ts配置项作用默认值routeTree传入由routeTree.gen.ts导出的完整路由树Router 据此匹配 URL无必填scrollRestoration开启后导航时自动恢复滚动位置配合 ScrollRestoration.tsx 使用falsedefaultPreload设置路由默认的预加载策略如intent悬停/聚焦时预加载、viewport等falsedefaultPreloadStaleTime预加载数据的过期时间毫秒决定多久后需要重新预加载0defaultStaleTime路由数据加载结果的默认过期时间毫秒0trailingSlash控制 URL 末尾斜杠的处理策略可选never、always、preservenevercaseSensitive路径匹配是否区分大小写falsebasepath应用部署在子路径下时的基准路径/context传入 Router 的上下文对象可在所有路由的beforeLoad、loader中访问无此外React 扩展层还支持defaultComponent、defaultErrorComponent、defaultPendingComponent、defaultNotFoundComponent、Wrap、InnerWrap等选项声明位置在 packages/react-router/src/router.ts其中Wrap/InnerWrap可用来包裹整个 Router 或 Router 内部内容以注入全局 Provider。File-Based Routing文件即路由Start 采用 TanStack Router 的**文件式路由File-Based Routing**方案以确保合理的代码分割与极致的类型安全。所有路由都放在src/routes目录下src/ ├── routes -- This is where you put your routes │ ├── __root.tsx │ ├── index.tsx │ ├── about.tsx │ ├── posts.tsx │ ├── posts/$postId.tsx文件系统的目录结构直接映射为路由层级posts.tsx对应/postsposts/$postId.tsx对应/posts/:postId。Start 的构建插件Vite 插件会扫描该目录并生成类型与路由树详见下文「Route Tree Generation」同时自动处理代码分割使每个路由的组件与 loader 按需加载。The Root Route根路由根路由是整个路由树中最顶层的路由它把其余所有路由都封装为自己的子路由位于src/routes/__root.tsx且必须命名为__root.tsxsrc/ ├── routes │ ├── __root.tsx -- The root route根路由具有以下特性没有路径并且永远会被匹配它的component永远会被渲染它是渲染文档外壳html、body等的地方因为它永远被渲染所以也是构建应用外壳、执行全局逻辑如鉴权守卫、全局 Provider、DevTools的最佳位置。一个典型的根路由实现如下来源当前仓库 docs/start/framework/react/guide/routing.md// src/routes/__root.tsx import { Outlet, createRootRoute, HeadContent, Scripts, } from tanstack/react-router import type { ReactNode } from react export const Route createRootRoute({ head: () ({ meta: [ { charSet: utf-8, }, { name: viewport, content: widthdevice-width, initial-scale1, }, { title: TanStack Start Starter, }, ], }), component: RootComponent, }) function RootComponent() { return ( RootDocument Outlet / /RootDocument ) } function RootDocument({ children }: Readonly{ children: ReactNode }) { return ( html head HeadContent / /head body {children} Scripts / /body /html ) }其中head: () ({ meta: [...] })用于声明文档级的 head 元信息字符集、viewport、标题等这些元信息会由HeadContent在head中统一渲染。请注意body底部的Scripts组件它负责加载应用的全部客户端 JavaScript必须始终保留才能保证应用正常运行。在真实示例 examples/react/basic-file-based/src/routes/__root.tsx 中可以看到同样的结构根组件渲染导航栏、Outlet /以及TanStackRouterDevtools。createRootRoute的 React 实现定义在 packages/react-router/src/route.tsx若你的应用需要在路由中注入类型化的上下文例如认证状态可以使用createRootRouteWithContextTRouterContext()工厂同文件 L400-L435它会强制约束createRouter传入的context类型。HeadContent渲染文档头部HeadContent组件用于渲染文档的head部分包括 title、meta、link 以及 head 相关的 script 标签。它应当被渲染在根路由布局的head标签内head HeadContent / /head从源码 packages/react-router/src/HeadContent.tsx 看HeadContent内部通过useTags收集所有已匹配路由声明的 head 标签每条路由都可以通过head: () ({ meta, links, scripts, ... })声明自己的元信息并逐个渲染为Asset标签同时它会读取router.options.ssr?.nonce将 nonce 透传给所有标签以支持 CSP内容安全策略场景。这是 TanStack Start 实现「文档头部管理」的关键机制——每个路由都能贡献自己的 title、meta 与 link且这些内容在 SSR 阶段就被序列化输出。Outlet渲染匹配的子路由Outlet组件用于渲染下一个可能匹配的子路由。Outlet /不接收任何 props可以渲染在任意路由组件树的任意位置如果没有匹配的子路由它会渲染为nullfunction RootComponent() { return ( RootDocument Outlet / /RootDocument ) }Outlet是实现嵌套路由布局的核心父路由如根路由负责渲染外壳与导航Outlet /的位置即子路由组件的挂载点。在嵌套层级较深时每一层的父路由都通过自己的Outlet /把控制权交给下一层匹配到的子路由。Scripts渲染文档主体脚本Scripts组件用于渲染文档的 body 脚本标签。它应当被渲染在根路由布局的body标签内通常放在body末尾body {children} Scripts / /body从源码 packages/react-router/src/Scripts.tsx 可以看到它的完整工作方式收集所有已匹配路由matches上声明的scripts标签在 SSR 场景下还会读取router.ssr?.manifest由 Start 构建时生成的资源清单中的manifest.routes[routeId]?.scripts把每个路由对应的客户端脚本如按路由代码分割产生的script src注入到 body 中服务端渲染时同步输出脚本客户端水合时则通过useSelector订阅匹配结果变化在路由切换后自动更新脚本标签与HeadContent一样支持通过router.options.ssr?.nonce透传 CSP nonce。这也是为什么在 SSR 应用中Scripts /是连接服务端渲染产物与客户端水合代码的关键桥梁——没有它客户端 JavaScript 不会被加载页面将无法交互。Route Tree Generation路由树自动生成你会在项目中注意到一个routeTree.gen.ts文件src/ ├── routeTree.gen.ts -- The generated route tree file这个文件在你运行 TanStack Startnpm run dev或npm run start时自动生成包含完整的生成路由树route tree一批 TS 工具类型使 TanStack Start 的类型安全推断「极快且完全推断」。正是通过这个生成文件createFileRoute(/posts/$postId)中的路径字符串、Link to/posts/$postId的目标、Route.useParams()/Route.useLoaderData()的返回值类型等全部被 TypeScript 静态约束。生成过程由 TanStack Router 的 Bundler 插件Vite 插件或 Router CLI 驱动在开发模式下文件变更会实时重新生成。由于routeTree.gen.ts由工具自动写入你不应手动编辑它同时建议在源码控制中保留该文件或按团队约定选择生成时机。作为对照可在仓库示例中查看已生成的成果例如 examples/react/basic-file-based/src/routeTree.gen.ts 与 examples/react/kitchen-sink-file-based/src/routeTree.gen.ts。Nested Routing嵌套路由TanStack Router 使用嵌套路由将 URL 与正确的组件树对应起来。例如给定以下路由routes/ ├── __root.tsx -- Renders the Root component ├── posts.tsx -- Renders the Posts component ├── posts.$postId.tsx -- Renders the Post component当 URL 为/posts/123时组件树将呈现为Root Posts Post / /Posts /Root也就是说URL 的每一段都会在路由树中找到对应层级父路由渲染自身布局后通过Outlet /把匹配到的子路由渲染出来层层嵌套直至叶子组件。这种「URL 段 → 路由层级 → 组件嵌套」的对应关系正是文件式路由目录结构的运行期体现。Types of Routes路由类型一览在项目中可以创建多种类型的路由Index Routes索引路由当 URL 与路由路径完全一致时匹配例如/匹配index.tsx/posts匹配posts/index.tsxDynamic/Wildcard/Splat Routes动态/通配路由动态捕获 URL 路径的一部分或全部到一个变量中供应用使用例如/posts/$postId捕获postId/rest/$捕获/rest/*的全部剩余路径。此外还有几种用于分组与组织的工具型路由Pathless Layout Routes无路径布局路由为一组路由应用布局或逻辑而不在路径中引入额外层级文件名以下划线开头如_auth.tsx其子路由为_auth.profile.tsxNon-Nested Routes非嵌套路由将路由从父路由中「解嵌套」渲染自己独立的组件树用于不希望套用父布局的场景Grouped Routes分组路由仅在目录层面把路由组织在一起不影响路径层级目录名用圆括号包裹如(this-folder-is-not-in-the-url)。这三种工具型路由的实战形态可在仓库示例 examples/react/basic-file-based/src/routes含_pathlessLayout.tsx、_pathlessLayout/_nested-layout.tsx与 examples/react/kitchen-sink-file-based/src/routes含(this-folder-is-not-in-the-url)/route-group.tsx、_auth.tsx、_auth.profile.tsx中对照查看。Route Tree Configuration路由树配置路由树完全通过src/routes目录来配置。目录中每个文件对应一个路由文件名即路由路径的声明方式目录结构即路由层级。你可以随时通过新增文件、移动文件、重命名文件来调整路由结构配套的 Bundler 插件 / Router CLI 会自动同步更新routeTree.gen.ts中的路径字符串与类型。Creating File Routes创建文件路由要创建一个路由只需新建一个与目标路径对应的文件即可。文件名到路径的映射规则如下PathFilenameType/index.tsxIndex Route/aboutabout.tsxStatic Routeposts.tsxLayout Route/posts/posts/index.tsxIndex Route/posts/:postIdposts/$postId.tsxDynamic Route/rest/*rest/$.tsxWildcard Route命名约定要点index.tsx表示索引路由匹配其所在目录的「裸路径」普通文件名如about.tsx映射为静态路径$前缀如$postId.tsx声明动态路径参数$.tsx声明通配splat路由匹配该层级下的所有剩余路径目录名带_前缀为无路径布局带()为纯分组目录见上文「Types of Routes」。Defining Routes定义路由使用createFileRoute函数导出名为Route的路由变量即可定义路由。例如要处理/posts/:postId路由需要创建posts/$postId.tsxsrc/ ├── routes │ ├── posts/$postId.tsx然后这样定义路由// src/routes/posts/$postId.tsx import { createFileRoute } from tanstack/react-router export const Route createFileRoute(/posts/$postId)({ component: PostComponent, })注意传给createFileRoute的路径字符串由 Router 通过 TanStack Router Bundler 插件或 Router CLI自动写入并维护。因此当你新建路由、移动路由或重命名路由时路径字符串会自动更新。从源码看createFileRoute定义在 packages/react-router/src/fileRoute.ts它接收文件路径字符串字面量返回一个接收路由选项并调用createRoute的函数。同时FileRoutesByPath等类型packages/router-core/src/fileRoute.ts将「文件路径 → 路由类型」的映射固化到类型系统中这正是「路径字符串写错会直接编译报错」的类型安全来源。在真实示例中查看完整路由定义仓库示例 examples/react/basic-file-based/src/routes/posts.$postId.tsx 展示了更完整的用法——在createFileRoute(/posts/$postId)的选项中同时配置loader从params中解构postId并异步获取数据、errorComponent与notFoundComponent并在组件中通过Route.useLoaderData()消费类型安全的加载结果export const Route createFileRoute(/posts/$postId)({ loader: async ({ params: { postId } }) fetchPost(postId), errorComponent: PostErrorComponent, notFoundComponent: () { return pPost not found/p }, component: PostComponent, }) function PostComponent() { const post Route.useLoaderData() return ( div classNamespace-y-2 h4 classNametext-xl font-bold underline{post.title}/h4 div classNametext-sm{post.body}/div /div ) }这也印证了createFileRoute选项的类型来自 packages/router-core/src/fileRoute.ts 中的FileRouteOptionsloader、beforeLoad、validateSearch、component、errorComponent、pendingComponent、notFoundComponent、head、wrap等都被约束为与路由类型严格匹配。深入源码文件路由类型系统的底层机制如果希望进一步理解「类型安全从何而来」可以沿着以下源码路径继续深挖packages/router-core/src/fileRoute.ts定义了FileRouteTypes、InferFileRouteTypes、FileRoutesByPath、FileRouteOptions、CreateFileRoute、LazyRouteOptions等核心类型是文件路由类型推导的根基packages/router-core/src/route.tsBaseRoute/BaseRootRoute的实现负责路径解析ResolveFullPath、ResolveId、ResolveParams、父子关系与路由 ID 的计算packages/react-router/src/route.tsxReact 层的createRoute、createRootRoute、createRootRouteWithContext、getRouteApi以及绑定在路由对象上的类型安全 hooksuseSearch、useParams、useLoaderData、useNavigate、Link等packages/react-router/src/router.tsReact 扩展的 Router 选项类型与createRouter实现packages/router-core/src/config.tsRouter 构造选项的完整类型定义RouterConstructorOptions可查阅全部可配置项及其注释。对于代码分割场景createLazyFileRoute与LazyRoutepackages/react-router/src/fileRoute.ts允许在.lazy.tsx文件中只声明非关键路由选项component、pendingComponent、errorComponent、notFoundComponent并将类型安全的 hooks 以预绑定形式提供给分割后的组件——这也是 Start 保证「每个路由的代码按需加载」同时不失类型安全的实现细节。小结从路由文件到完整类型安全应用回顾全文TanStack Start 的路由体系可以用一条链路概括在src/routes目录创建文件 → Bundler 插件/CLI 生成routeTree.gen.ts→src/router.tsx中通过getRouter创建 Router → 根路由__root.tsx渲染文档外壳HeadContentOutlet /Scripts→ 嵌套路由层层匹配渲染。理解这一链路后你便掌握了 Start 应用的路由骨架文件即路由、类型由生成文件自动推断、文档头部与脚本由HeadContent/Scripts统一托管、SSR 与客户端共享同一套路由配置。本文只是 Start 路由机制的入门概述。更深入的主题——例如路径参数的高级校验validateSearch、数据加载与缓存、预加载策略、非嵌套路由的细节行为——可以继续查阅仓库中的 docs/start/framework/react/guide 目录以及 docs/router/guide 目录下的路由与数据加载相关文档并结合 examples/react/basic-file-based 与 examples/react/kitchen-sink-file-based 两个示例项目动手实践。【免费下载链接】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),仅供参考
返回列表