尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

TanStack Router 混合路由实战:在 File-Based 路由树中嵌入 Virtual Routes

TanStack Router 混合路由实战:在 File-Based 路由树中嵌入 Virtual Routes TanStack Router 混合路由实战在 File-Based 路由树中嵌入 Virtual Routes【免费下载链接】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本指南以仓库中的basic-virtual-inside-file-based示例为蓝本系统讲解如何在 TanStack Router 的文件式File-Based路由体系中嵌入虚拟路由Virtual Routes通过__virtual.ts与tanstack/virtual-file-routes提供的index、route、physical等 API在保留文件路由的类型安全与约定式能力的同时动态生成、挂载甚至嫁接物理目录作为子路由。读完本文你将掌握混合路由的目录组织方式、physical路径前缀挂载的语义、嵌套虚拟子树的写法以及它们如何被统一编译进routeTree.gen.ts的类型系统。一、为什么需要文件路由中的虚拟路由TanStack Router 默认提供两套路由声明方式基于文件系统约定的 File-Based Routing文件即路由以及完全由代码声明、可动态计算的 Virtual File Routes。两者各有优势文件路由直观、可维护性强且能获得完整的类型推断虚拟路由则允许在运行时用代码生成路由适合动态、程序化、无法用静态文件表达的路径。本示例演示的正是两者的交汇点——Hybrid混合路由以文件式路由为主体骨架在任意目录内放置__virtual.ts配置文件将虚拟生成的路由与目录内真实存在的物理路由文件组合成同一棵路由树。从示例说明examples/react/basic-virtual-inside-file-based/README.md来看它聚焦于四项能力组合虚拟与文件式路由、混合路由方法、在文件式结构中动态生成路由、以及灵活的路由配置。这意味着你不再需要在全文件与全虚拟之间二选一而是可以在一个大型应用中按需混用。二、示例工程结构总览先看完整目录结构examples/react/basic-virtual-inside-file-basedsrc/ ├── main.tsx # 入口创建 Router、注册类型 ├── posts.tsx # 数据层fetchPosts / fetchPost ├── routeTree.gen.ts # 插件自动生成的路由树含虚拟路由 └── routes/ ├── __root.tsx # 根路由含 notFoundComponent、Devtools ├── _layout.tsx # 一级布局路由 ├── _layout/ │ └── _layout-2.tsx # 二级布局路由 │ └── _layout-2/ │ ├── layout-a.tsx │ └── layout-b.tsx ├── index.tsx # 首页 ├── posts.tsx # /posts 文件路由带 loader └── posts/ ├── home.tsx # /posts/ 索引页 ├── details.tsx # /posts/$postId 动态详情页 ├── __virtual.ts # ★ 虚拟路由配置关键 └── lets-go/ # ★ 被虚拟配置挂载的物理目录 ├── __virtual.ts # ★ 嵌套虚拟子树 ├── index.tsx └── deeper/ ├── __virtual.ts └── home.tsx其中routes/posts/目录同时包含普通文件路由home.tsx、details.tsx与虚拟配置__virtual.ts这就是虚拟嵌入文件路由的字面含义而lets-go/则是一个物理目录它通过虚拟配置被整体挂载到指定路径前缀下。三、核心机制__virtual.ts与defineVirtualSubtreeConfig虚拟路由的入口是一个名为__virtual.ts的配置文件TanStack Router 的 Vite 插件会扫描所有以该名字结尾的文件并把其中的声明并入路由树。示例中routes/posts/__virtual.ts源码内容如下import { defineVirtualSubtreeConfig, index, physical, route, } from tanstack/virtual-file-routes // 演示可以用 async 函数声明虚拟路由 export default defineVirtualSubtreeConfig(async () [ index(home.tsx), route($postId, details.tsx), physical(/inception, lets-go), ])defineVirtualSubtreeConfig是来自tanstack/virtual-file-routes的类型辅助函数其实现packages/virtual-file-routes/src/defineConfig.ts非常简单——它只做类型层面的收窄把配置原样返回支持四种形式形式说明直接数组VirtualRouteSubtreeConfig即VirtualRouteNode[]Promise异步解析出配置数组返回数组的函数() VirtualRouteSubtreeConfig返回 Promise 的函数() PromiseVirtualRouteSubtreeConfig示例注释明确说明可以用 async 函数定义虚拟路由因此上面配置中async () [...]的形式完全合法——这也意味着你可以在运行时从任意数据源数据库、远程配置、后端接口动态计算路由。从源码实现看defineVirtualSubtreeConfig直接透传返回值真正的解析与合并逻辑由tanstack/router-plugin在构建/开发阶段完成。配置项的语义该__virtual.ts声明了三类路由节点index(home.tsx)声明一个索引路由文件指向当前目录下的home.tsx最终生成/posts/即/posts的索引页见 home.tsx。route($postId, details.tsx)声明一个带路径参数$postId的路由文件为details.tsx生成/posts/$postId见 details.tsx。physical(/inception, lets-go)把物理目录lets-go以路径前缀/inception挂载到当前虚拟子树下。注意home.tsx与details.tsx虽然也是磁盘上的真实文件但它们的路由身份是由__virtual.ts声明的——这正是虚拟路由与文件路由的差异所在文件路由的路径由文件位置决定而虚拟路由的路径由配置显式指定文件只是组件的载体。四、虚拟路由 API 详解index / route / physical / layout上述配置用到的 API 全部来自tanstack/virtual-file-routes其完整实现位于 packages/virtual-file-routes/src/api.ts。除示例中用到的三个函数外还有rootRoute与layout可用于声明更完整的虚拟路由树index(file)export function index(file: string): IndexRoute声明索引路由file为相对当前__virtual.ts所在目录的文件路径。渲染时对应路径/在/posts子树中即/posts/。route(path, file?) / route(path, children?)route(path: string, children: ArrayVirtualRouteNode): Route route(path: string, file: string): Route route(path: string, file: string, children: ArrayVirtualRouteNode): Route声明路径为path的路由。若fileOrChildren是字符串则指定组件文件若是数组则该节点只作为中间层不渲染组件继续嵌套子路由。path支持动态段如$postId。physical(pathPrefix?, directory)physical(pathPrefix: string, directory: string): PhysicalSubtree physical(directory: string): PhysicalSubtree这是混合路由的关键函数源码注释api.ts给出了明确语义将物理目录directory相对 routes 目录中的路由文件挂载到路径前缀pathPrefix下单参数调用等价于physical(, directory)即把该目录的路由合并到当前层级。示例中的physical(/inception, lets-go)即把routes/posts/lets-go/下的文件全部以/inception为前缀挂载。layout / rootRoutelayout(file: string, children): LayoutRoute layout(id: string, file: string, children): LayoutRoute rootRoute(file: string, children?): VirtualRootRoutelayout用于在虚拟树中声明布局路由可省略id或显式指定id以便复用同一布局文件rootRoute用于声明虚拟根路由。这两个 API 让__virtual.ts不仅能定义叶子路由还能表达完整的嵌套布局结构。五、物理目录挂载的语义physical(/inception, lets-go)要真正理解虚拟嵌入文件式的混合效果最直观的方式是观察插件生成的路由树 routeTree.gen.ts。文件顶部导入语句显示lets-go/目录下的物理文件被当作虚拟节点的子路由导入import { Route as PostsLetsGoIndexRouteImport } from ./routes/posts/lets-go/index import { Route as PostsLetsGoDeeperHomeRouteImport } from ./routes/posts/lets-go/deeper/home而在路由节点定义中这两个物理文件被赋予了/inception前缀的路径const PostsLetsGoIndexRoute PostsLetsGoIndexRouteImport.update({ id: /inception/, path: /inception/, getParentRoute: () PostsRoute, } as any) const PostsLetsGoDeeperHomeRoute PostsLetsGoDeeperHomeRouteImport.update({ id: /inception/deeper/, path: /inception/deeper/, getParentRoute: () PostsRoute, } as any)由此可以看到混合路由的完整链路lets-go/index.tsx是磁盘上的普通路由文件但它不再按文件位置解析为/posts/lets-go/而是根据__virtual.ts中的physical(/inception, lets-go)被解析为/posts/inception/。lets-go/deeper/home.tsx中的home.tsx之所以生效是因为deeper/内还有一份嵌套的__virtual.ts详见下一节它把home.tsx声明为索引路由最终路径为/posts/inception/deeper/。生成的路由类型接口FileRoutesByFullPath、FileRoutesByTo、FileRoutesById均自动包含这些虚拟路径开发者在Link to/posts/inception或useParams中都能获得与文件路由完全一致的编译期类型检查。也就是说physical的本质是给一个现有物理目录换一个路径身份让团队可以按目录组织组件文件同时对外暴露任意想要的 URL 结构甚至把多个目录挂到同一前缀下动态拼装路由。六、嵌套虚拟子树lets-go/deeper/__virtual.ts虚拟配置支持无限层级嵌套。lets-go/deeper/__virtual.ts源码是对physical挂载目录内部虚拟配置的演示import { defineVirtualSubtreeConfig, index, } from tanstack/virtual-file-routes export default defineVirtualSubtreeConfig([index(home.tsx)])这份配置位于physical挂载的lets-go目录的更深一层声明home.tsx为当前层级的索引路由。结合上层配置最终路由为最终 URL组件文件声明来源/posts/inception/routes/posts/lets-go/index.tsxphysical(/inception, lets-go)自动解析目录索引/posts/inception/deeper/routes/posts/lets-go/deeper/home.tsxdeeper/__virtual.ts中的index(home.tsx)验证方法deeper/home.tsx的组件实现为createFileRoute(/posts/inception/deeper/)源码与生成的路由树完全对应。这说明__virtual.ts既可以出现在普通文件路由目录下如routes/posts/也可以出现在被physical挂载的物理目录内部两层机制可以自由组合、递归嵌套。七、虚拟路由与文件路由的融合点混合路由的价值在于虚拟节点与文件节点共享同一套 TanStack Router 能力。示例中/posts本身是文件路由它的两个虚拟子路由home.tsx、details.tsx与其他文件子路由共同成为其 children见routeTree.gen.ts中PostsRouteChildren的组装。融合体现在三方面1. Loader 与数据获取/posts文件路由在 posts.tsx 中通过createFileRoute(/posts)({ loader: fetchPosts, ... })拉取文章列表虚拟声明的$postId详情页在 details.tsx 中同样可以使用loader、errorComponent、notFoundComponentexport const Route createFileRoute(/posts/$postId)({ loader: async ({ params: { postId } }) fetchPost(postId), errorComponent: PostErrorComponent, notFoundComponent: () pPost not found/p, component: PostComponent, })数据层 src/posts.tsx 使用redaxios请求 jsonplaceholder 接口并在 404 时抛出notFound()交由路由层处理——虚拟路由与文件路由在这些能力上没有任何差别。2. 布局路由共存示例保留了完整的文件式布局结构_layout.tsx与_layout/_layout-2.tsx源码构成两级嵌套布局/layout-a、/layout-b挂在布局下与此同时/posts子树中的虚拟路由并列存在于同一根路由下。文件布局与虚拟路由互不干扰共用Outlet渲染机制。3. 类型安全与运行时注册入口文件 main.tsx 中通过declare module tanstack/react-router注册 Router 实例虚拟路径随之进入全局类型同时开启了defaultPreload: intent、defaultStaleTime: 5000、scrollRestoration: true等默认行为虚拟路由同样受这些配置约束。根路由 __root.tsx 还配置了notFoundComponent并接入了TanStackRouterDevtools便于调试虚拟路由的匹配结果。八、启用混合路由的前提Vite 插件配置__virtual.ts之所以能被解析依赖 vite.config.ts 中的路由插件import { defineConfig } from vite import react from vitejs/plugin-react import { tanstackRouter } from tanstack/router-plugin/vite import tailwindcss from tailwindcss/vite export default defineConfig({ plugins: [ tailwindcss(), tanstackRouter({ target: react, autoCodeSplitting: true, }), react(), ], })两个要点其一tanstackRouter()插件必须启用它负责扫描__virtual.ts并生成routeTree.gen.ts其二autoCodeSplitting: true开启自动代码分割虚拟路由的组件如home.tsx、details.tsx会按路由自动拆包这也是虚拟路由与文件路由共享的构建期优化。项目依赖中包含tanstack/router-plugin与tanstack/virtual-file-routes见 package.json两者缺一不可。九、运行与验证按示例 README 的操作步骤即可本地体验脚本定义见 package.jsonpnpm install # 安装依赖 pnpm dev # 启动开发服务器vite --port 3000浏览器访问http://localhost:3000依次验证/posts进入文章列表页文件路由 loader 数据/posts/1虚拟声明$postId的动态详情页/posts/inception由physical(/inception, lets-go)重定身份后的物理目录索引/posts/inception/deeper嵌套__virtual.ts声明的深层路由/layout-a、/layout-b文件式嵌套布局。生产构建pnpm build # vite build tsc --noEmitbuild脚本在打包后还会执行tsc --noEmit做全量类型检查任何虚拟路由的路径拼写错误都会在编译期暴露——这正是混合路由与纯字符串路由方案相比的核心优势。十、小结何时使用混合路由从本示例可以总结出混合路由的三类典型诉求路径与文件解耦团队希望按领域组织组件文件lets-go/但对外 URL 需要更友好的结构/inception用physical完成目录挂载 前缀重写。动态路由生成路由来自异步数据或远程配置时用defineVirtualSubtreeConfig(async () [...])在构建期计算路由树。渐进式迁移已有文件式项目需要引入虚拟路由能力时无需推翻现有结构只需在目标目录放置__virtual.ts即可局部混用。相关 API 的完整实现可在 packages/virtual-file-routes/src/api.ts 与 packages/virtual-file-routes/src/defineConfig.ts 中继续研读仓库内还有basic-virtual-file-based、basic-file-based等对比示例位于 examples/react 下分别对应纯虚拟与纯文件两种极端形态与本文的混合形态互为参照。【免费下载链接】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),仅供参考
返回列表