ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

TanStack Router 入门实战:从 basic 示例掌握路由配置、导航、参数与数据加载

TanStack Router 入门实战:从 basic 示例掌握路由配置、导航、参数与数据加载 TanStack Router 入门实战从 basic 示例掌握路由配置、导航、参数与数据加载【免费下载链接】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本文以当前仓库中的 examples/react/basic 示例为蓝本系统拆解 TanStack Router 在 React 应用中的最小可用形态如何用代码声明方式构建路由树、配置导航与激活态、处理路径参数与数据加载、实现懒加载与路径无关布局并接入 DevTools 与全链路类型安全。读完本文你将能够不依赖文件路由插件仅用createRootRoute/createRoute/createRouter三件套徒手搭建一个完整、可运行的 TanStack Router 应用并理解其底层路由注册与类型推导机制。示例概览一个麻雀虽小五脏俱全的路由应用basic是 TanStack Router 最基础的 React 示例它刻意不使用文件路由file-based routing而是用最直白的代码声明方式把路由树写出来非常适合理解路由的核心抽象。整个示例只有 5 个源文件却完整覆盖了下列能力这也是原文档 README 中列出的主题基础路由设置与路由树构建createRootRoute、createRoute、addChildren路由配置路径、布局、loader、错误处理、404 组件导航Link组件、激活态样式、路径参数传参路由参数$postId动态段数据加载loaderuseLoaderData以及预加载与缓存策略TanStack Router DevTools项目文件结构如下examples/react/basic/ ├── index.html # 入口 HTML挂载点 div idapp ├── package.json # 依赖与 npm scripts ├── tsconfig.json # 严格模式 TypeScript 配置 ├── vite.config.js # Vite React Tailwind CSS 插件 └── src/ ├── main.tsx # 路由树定义、Router 实例与应用挂载 ├── posts.ts # 数据获取层模拟延迟 404 错误 ├── posts.lazy.tsx # 懒加载的 /posts 布局组件 ├── styles.css # Tailwind CSS 入口 └── vite-env.d.ts其中 src/main.tsx 是路由定义的唯一来源承载了本示例 90% 的技术要点下文将逐段拆解。快速开始安装、启动与构建原文档给出了完整的命令流程这里结合 package.json 中的脚本逐一说明。安装依赖pnpm install依赖清单中核心运行库为tanstack/react-router与tanstack/react-router-devtools当前仓库中对应的源码位于 packages/react-router 与 packages/react-router-devtoolsUI 层使用 React 19网络请求使用redaxios一个轻量 axios 替代品样式使用 Tailwind CSS 4。启动开发服务器pnpm dev对应脚本为vite --port3000即 Vite 开发服务器固定监听 3000 端口。开发模式下 Vite 提供 HMR 热更新修改src/main.tsx等文件后页面会即时刷新。生产构建pnpm build对应脚本为vite build tsc --noEmit先用 Vite 打包再用 TypeScript 编译器做全量类型检查。这意味着类型错误会导致构建失败这正是 TanStack Router 类型安全体系的强制保障。预览与直启pnpm preview # 本地预览生产构建产物 pnpm start # 等价于 pnpm dev直接启动 Vite基于本示例创建新项目原文档还提供了一条基于该示例初始化新项目的快捷命令npx gitpick TanStack/router/tree/main/examples/react/basic basicgitpick会将该示例模板拉取到当前目录并命名为basic随后即可按上面的流程安装、开发。如果你只想快速搭建也可以直接复制本仓库 examples/react/basic 目录中的文件。路由树的构建三件套与 addChildrenTanStack Router 的核心抽象是路由树route tree每个路由通过getParentRoute指向父路由再经addChildren组装成树。示例中这一过程全部发生在 src/main.tsx。根路由createRootRouteconst rootRoute createRootRoute({ component: RootComponent, notFoundComponent: () { return ( div pThis is the notFoundComponent configured on root route/p Link to/Start Over/Link /div ) }, })createRootRoute用于创建整棵路由树的根节点可配置全局布局组件、全局 404 组件、错误组件等。其底层实现位于 packages/react-router/src/route.tsx#L551-L603它接收RootRouteOptions返回一个RootRoute实例。这里通过notFoundComponent配置了全局 404 兜底当 URL 匹配不到任何路由时示例中导航栏专门放了一个This Route Does Not Exist链接用于演示会渲染这段提示并提供Start Over回到首页的链接。普通路由createRouteconst indexRoute createRoute({ getParentRoute: () rootRoute, path: /, component: IndexComponent, })createRoute接收一个getParentRoute回调返回父路由实例与路由配置对象。从源码 packages/react-router/src/route.tsx#L295 可以看出createRoute是一个高度泛型化的工厂函数其泛型参数涵盖注册表、search 校验器、路由上下文、loader 类型等——这正是后续类型安全注册机制的基础每个路由的路径、参数、loader 返回值都会在编译期被精确推导。组装路由树addChildrenconst routeTree rootRoute.addChildren([ postsLayoutRoute.addChildren([postRoute, postsIndexRoute]), pathlessLayoutRoute.addChildren([ nestedPathlessLayout2Route.addChildren([ pathlessLayoutARoute, pathlessLayoutBRoute, ]), ]), indexRoute, ])addChildren将子路由挂到父路由下形成一棵三层的嵌套路由树根路由下挂载了posts布局、路径无关布局pathless layout与首页三个分支。父子关系决定了组件嵌套关系父路由的组件渲染Outlet /子路由的组件就渲染在 Outlet 位置。RootComponent 中正是用Outlet /承载子路由内容。导航Link 组件与激活态示例的导航栏使用 TanStack Router 的Link组件src/main.tsx#L32-L65Link to/ activeProps{{ className: font-bold }} activeOptions{{ exact: true }} Home /Link Link to/posts activeProps{{ className: font-bold }} Posts /LinkLink的要点to目标路径受路由树类型约束——不存在的路径会在编译期直接报错。示例中刻意用// ts-expect-error标注了一个to/this-route-does-not-exist的链接正是为了演示引用不存在路由会在类型层面失败这一特性。activeProps当前 URL 命中该路由时附加的 props如加粗高亮是渲染当前所在导航项的推荐方式。activeOptions{{ exact: true }}仅当路径完全匹配时才视为激活。不加exact时/posts链接在/posts/123页面也会保持高亮两者语义不同可按需选用。路径参数$postId 动态段TanStack Router 以$前缀声明动态路径段。示例中文章详情路由const postRoute createRoute({ getParentRoute: () postsLayoutRoute, path: $postId, errorComponent: PostErrorComponent, loader: ({ params }) fetchPost(params.postId), component: PostComponent, })path: $postId声明一个动态段任何/posts/xxx都会匹配到该路由xxx即postId。loader 回调接收{ params }可直接取出params.postId发起数据请求。在懒加载布局 posts.lazy.tsx 中列表项通过Link携带参数导航Link to/posts/$postId params{{ postId: post.id }} activeProps{{ className: font-bold underline }} div{post.title.substring(0, 20)}/div /Linkparams对象同样受到类型约束postId必须是合法字段且参数类型string在编译期校验拼错字段名或漏传参数都会报错。组件侧取参则使用postRoute.useLoaderData()function PostComponent() { const post postRoute.useLoaderData() return ( div classNamespace-y-2 h4 classNametext-xl font-bold{post.title}/h4 div classNametext-sm{post.body}/div /div ) }useLoaderData是 TanStack Router 的类型安全数据访问钩子loader的返回值类型会被精确推导到组件中post.title/post.body无需任何断言即可获得完整补全与类型检查。除useLoaderData外TanStack Router 还提供useParams、useSearch、useNavigate、useRouterState等配套钩子分别用于取参数、取 search 状态、编程式导航与订阅路由状态。数据加载loader、预加载与缓存loader 与异步数据路由的loader负责在路由进入前准备数据export const postsLayoutRoute createRoute({ getParentRoute: () rootRoute, path: posts, loader: () fetchPosts(), }).lazy(() import(./posts.lazy).then((d) d.Route))数据获取逻辑集中在 src/posts.tsexport const fetchPosts async () { console.info(Fetching posts...) await new Promise((r) setTimeout(r, 500)) return axios .getArrayPostType(https://jsonplaceholder.typicode.com/posts) .then((r) r.data.slice(0, 10)) } export const fetchPost async (postId: string) { console.info(Fetching post with id ${postId}...) await new Promise((r) setTimeout(r, 500)) const post await axios .getPostType(https://jsonplaceholder.typicode.com/posts/${postId}) .then((r) r.data) if (!post) { throw new NotFoundError(Post with id ${postId} not found!) } return post }两个 fetch 函数都人为模拟了 500ms 网络延迟便于在 DevTools 中观察加载时序fetchPost在数据不存在时抛出自定义的NotFoundError用于演示路由级错误处理。请求目标为公开的 jsonplaceholder 假数据接口开箱即用无需后端。全局路由配置createRouter路由树组装完成后通过createRouter创建 Router 实例src/main.tsx#L214-L219const router createRouter({ routeTree, defaultPreload: intent, defaultStaleTime: 5000, scrollRestoration: true, })routeTree必填上一步组装好的路由树。defaultPreload: intent默认预加载策略。intent表示当用户把鼠标悬停或移动端触碰按压到Link上时提前触发目标路由的loader使导航瞬间完成TanStack Router 支持的取值还包括false关闭与render链接渲染时即预加载。createRouter的实现位于 packages/react-router/src/router.ts#L91。defaultStaleTime: 5000loader 数据的新鲜期为 5000ms。在此时间内再次进入同一路由不会重新执行loader而是直接复用缓存避免重复请求超过后才会重新拉取。scrollRestoration: true启用滚动位置恢复导航返回时自动还原页面滚动位置。类型安全注册declare moduleTanStack Router 类型推导的最后一公里是类型注册src/main.tsx#L222-L226declare module tanstack/react-router { interface Register { router: typeof router } }把 router 实例类型注册进tanstack/react-router模块后全项目范围内Link的to、params、searchuseLoaderData的返回类型、useNavigate的目标路径等都会被这个实例的路由树类型约束。这是 TanStack Router 相比传统字符串路由方案最核心的差异路由即类型类型即文档非法路径在编译期就会被拦截。最后挂载应用const rootElement document.getElementById(app)! if (!rootElement.innerHTML) { const root ReactDOM.createRoot(rootElement) root.render(RouterProvider router{router} /) }if (!rootElement.innerHTML)的守卫是为了兼容 SSR 场景——若服务端已渲染内容客户端不重复渲染避免水合冲突。挂载点#app定义在 index.html 中。懒加载路由lazy 与 createLazyRoute示例演示了两种代码拆分方式二者结合使用路由级懒加载createRoute(...).lazy(() import(./posts.lazy).then((d) d.Route))把posts布局的组件实现拆到独立 chunk只有访问/posts时才加载。模块内 lazy routeposts.lazy.tsx 使用createLazyRoute(/posts)定义懒加载路由import { Link, Outlet, createLazyRoute } from tanstack/react-router export const Route createLazyRoute(/posts)({ component: PostsLayoutComponent, }) function PostsLayoutComponent() { const posts Route.useLoaderData() // ...渲染文章列表 Outlet / }createLazyRoute的底层实现在 packages/react-router/src/fileRoute.ts#L262其路径参数/posts与主路由严格对应。这种模式下组件与数据逻辑分离主路由文件只保留路径、loader 等配置服务端/构建期需要的信息组件等体积较大的部分按需加载首屏只加载必要代码。列表渲染时向数组中追加了一个id: i-do-not-exist的假条目用于演示参数导航到不存在文章时的 404 错误处理链路。路径无关布局Pathless Layout示例用三层路径无关布局pathless layout展示了不影响 URL 的组件嵌套能力src/main.tsx#L130-L200const pathlessLayoutRoute createRoute({ getParentRoute: () rootRoute, id: _pathlessLayout, // 用 id 而非 path 标识 component: PathlessLayoutComponent, }) const nestedPathlessLayout2Route createRoute({ getParentRoute: () pathlessLayoutRoute, id: _nestedPathlessLayout, component: PathlessLayout2Component, }) const pathlessLayoutARoute createRoute({ getParentRoute: () nestedPathlessLayout2Route, path: /route-a, component: PathlessLayoutAComponent, })关键点不写path改用id以下划线开头是约定俗成的命名该路由就不会向 URL 贡献任何路径段。访问/route-a时实际渲染链为RootComponent → PathlessLayoutComponent → PathlessLayout2Component → PathlessLayoutAComponentURL 却保持/route-a不变。这种布局壳非常适合做权限校验、主题包裹、多级 UI 框架等场景既享受组件嵌套又不污染 URL 语义。错误处理errorComponent 与 NotFoundError文章详情路由配置了错误组件const postRoute createRoute({ getParentRoute: () postsLayoutRoute, path: $postId, errorComponent: PostErrorComponent, loader: ({ params }) fetchPost(params.postId), component: PostComponent, }) function PostErrorComponent({ error }: ErrorComponentProps) { if (error instanceof NotFoundError) { return div{error.message}/div } return ErrorComponent error{error} / }errorComponent接收ErrorComponentProps其中error为 loader 抛出的异常。自定义NotFoundError定义在 posts.ts用于区分业务 404与其他意外错误数据不存在时渲染友好提示Post with id xxx not found!其余错误则交给框架内置的ErrorComponent渲染通用错误界面。访问/posts/i-do-not-exist来自列表中的假条目即可看到该错误路径的完整表现。配合根路由的notFoundComponentURL 完全不匹配任何路由时的 404与errorComponent路由内抛错时的错误界面示例形成了完整的未匹配 → 404、已匹配但失败 → 错误页双轨处理体系。工程配置Vite、TypeScript 与样式Vite 配置vite.config.js 仅注册了两个插件export default defineConfig({ plugins: [tailwindcss(), react()], })vitejs/plugin-react提供 JSX 转换与 Fast Refreshtailwindcss/vite是 Tailwind CSS 4 的官方 Vite 插件二者共同支撑开发体验。TypeScript 配置tsconfig.json 的关键选项strict: true严格模式与 TanStack Router 的类型安全理念一致jsx: react-jsxReact 17 自动 JSX runtime无需显式import ReactmoduleResolution: Bundler与module: ESNext适配 Vite 的打包器解析策略target: ESNext、lib: [DOM, DOM.Iterable, ES2022]面向现代浏览器的目标环境。样式入口styles.css 通过import tailwindcss source(../)引入 Tailwind并设置了基础层颜色变量与明暗双主题color-scheme: light dark页面的灰色系背景/文字随系统主题自动切换。运行与验证按以下顺序即可完整体验本示例的全部特性pnpm install安装依赖pnpm dev启动开发服务器浏览器访问http://localhost:3000依次点击导航栏的Home / Posts / Pathless Layout / This Route Does Not ExistHome首页路由Posts触发fetchPostsConsole 打印 Fetching posts...列表渲染 10 篇文章 1 个假条目点击进入/posts/{id}详情详情页展示post.title与post.body点击假条目i-do-not-exist观察NotFoundError消息渲染Pathless LayoutURL 保持/route-a或/route-b但页面被两层 pathless layout 包裹This Route Does Not Exist触发根路由notFoundComponent的 404 提示右下角的TanStack Router DevTools面板可实时查看当前匹配的路由、loader 状态、search/params 与路由树结构——这也是原文档强调的 DevTools 集成能力开发时建议始终开启。若需验证生产构建与类型检查运行pnpm build任何路由类型错误都会使构建失败。小结从 basic 示例看 TanStack Router 的设计哲学通读 examples/react/basic 的源码可以看到TanStack Router 的核心竞争力并非某个单一 API而是一套贯穿始终的类型安全体系路由即数据createRoute的泛型签名packages/react-router/src/route.tsx把路径、参数、loader、上下文全部纳入类型推导类型即文档declare module注册src/main.tsx#L222-L226让Link.to、useLoaderData等 API 在编辑器内获得完整补全与错误提示渐进增强从代码路由本示例到文件路由、从同步 loader 到预加载与缓存、从单一入口到懒加载拆分能力边界清晰、可按需选用。若希望在此基础上进一步深入可继续阅读当前仓库的 react-router 官方文档快速上手、核心 API 文档或对比文件路由实现 basic-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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进