前言
大家好,我是 hyy,一个热爱技术分享的全栈工程师。
作为一个有着丰富经验的商业型 TypeScript 全栈开发者,我深知技术分享的重要性。我的座右铭是"深入浅出,知其然知其所以然",因为我始终相信,只有真正理解了基础,才能在技术的道路上走得更远。
在这篇文章中,我将带你深入浅出地了解 TanStack Router 这个强大的路由工具。无论你是初学者还是有经验的开发者,相信都能从中获得启发。
一、为什么选择 TanStack Router?
传统路由的痛点
-
类型安全问题
- 路由参数类型不确定
- 路由配置缺乏类型检查
- 路由跳转参数容易出错
-
状态管理复杂
- URL 参数与应用状态同步困难
- 需要手动管理路由状态
- 页面间数据传递不便
-
代码组织问题
- 路由配置分散
- 文件组织结构不清晰
- 路由与组件耦合度高
二、TanStack Router 路由模式详解
1. 基于文件的路由生成(File-Based Route Generation)
- 推荐使用的方式
- 自动根据文件结构生成路由
- 提供最佳的开发体验和性能
- 支持自动代码分割
2. 基于代码的路由配置(Code-Based Route Configuration)
- 传统的路由配置方式
- 更灵活的路由控制
- 适合特殊的路由需求
- 手动管理路由结构
3. 虚拟文件路由(Virtual File Routes)
- 结合了前两种方式的优点
- 支持动态路由生成
- 适合复杂的路由场景
- 保持类型安全
三、文件路由(File-Based Route Generation)最佳实践
1. 项目初始化
# 使用脚手架创建项目
npm create @tanstack/router@latest
# 或
pnpm create @tanstack/router
2. 基础依赖安装
# 安装核心依赖
npm install @tanstack/react-router
# 安装开发依赖
npm install -D @tanstack/router-plugin @tanstack/router-devtools
3. Vite 配置
// vite.config.ts
import { defineConfig } from "vite";
import viteReact from "@vitejs/plugin-react";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
export default defineConfig({
plugins: [
TanStackRouterVite({
autoCodeSplitting: true, // 启用自动代码分割
}),
viteReact(),
],
});
4. 路由文件结构
src/
routes/
__root.tsx # 根路由配置
index.tsx # 首页路由
about.tsx # 关于页面路由
users/
index.tsx # 用户列表页
$userId.tsx # 用户详情页
5. 路由实现示例
// $productId.tsx
import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/products/$productId")({
loader: async ({ params: { productId } }) => {
return fetchProduct(productId);
},
component: ProductDetail,
});
首页路由
// index.tsx
import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/")({
component: Index,
});
function Index() {
return (
<div>
<h1>欢迎访问首页</h1>
</div>
);
}
动态路由示例
// $userId.tsx
import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/users/$userId")({
component: UserDetail,
loader: async ({ params: { userId } }) => {
return fetchUserData(userId);
},
});
function UserDetail() {
const { userId } = Route.useParams();
const user = Route.useLoaderData();
return (
<div>
<h2>用户详情: {userId}</h2>
<pre>{JSON.stringify(user, null, 2)}</pre>
</div>
);
}
6. 路由入口配置
// main.tsx
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
const router = createRouter({ routeTree });
// 类型注册
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}
ReactDOM.createRoot(document.getElementById("root")!).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>
);
四、实际业务场景
1. 电商产品列表与筛选
// 问题:如何处理复杂的筛选、排序和分页参数?
export const Route = createLazyFileRoute("/products")({
searchSchema: z.object({
category: z.string().optional(),
price: z.enum(["asc", "desc"]).optional(),
page: z.number().min(1).default(1),
}),
loader: async ({ search }) => {
return fetchProducts(search);
},
component: ProductList,
});
// 使用示例
export function ProductFilters() {
const navigate = useNavigate();
const search = useSearch();
return (
<select
value={search.category}
onChange={(e) => {
navigate({
search: (prev) => ({
...prev,
category: e.target.value,
page: 1, // 重置页码
}),
});
}}
>
<option value="electronics">电子产品</option>
<option value="clothing">服装</option>
</select>
);
}
2. 多步骤表单
// 问题:如何管理多步骤表单的状态和导航?
export const Route = createLazyFileRoute("/checkout")({
searchSchema: z.object({
step: z.enum(["shipping", "payment", "review"]).default("shipping"),
}),
beforeLoad: async () => {
// 检查购物车是否为空
const cart = await getCart();
if (cart.isEmpty) {
throw redirect({ to: "/cart" });
}
},
component: CheckoutPage,
});
// 步骤导航组件
export function CheckoutNavigation() {
const { step } = useSearch();
const navigate = useNavigate();
const goToNext = () => {
const steps = ["shipping", "payment", "review"];
const currentIndex = steps.indexOf(step);
if (currentIndex < steps.length - 1) {
navigate({
search: { step: steps[currentIndex + 1] },
});
}
};
return <button onClick={goToNext}>下一步</button>;
}
3. 权限路由
// 问题:如何处理路由权限和认证?
export const Route = createLazyFileRoute("/admin")({
beforeLoad: async () => {
const user = await getUser();
if (!user?.isAdmin) {
throw redirect({
to: "/login",
search: { returnTo: "/admin" },
});
}
},
component: AdminLayout,
});
4. 模态框路由
// 问题:如何处理模态框的路由状态?
export const Route = createLazyFileRoute("products/$productId")({
searchSchema: z.object({
showReviews: z.boolean().default(false),
}),
component: ProductDetail,
});
export function ProductDetail() {
const { showReviews } = useSearch();
const navigate = useNavigate();
return (
<div>
<ProductInfo />
{showReviews && (
<Modal
onClose={() => {
navigate({
search: (prev) => ({ ...prev, showReviews: false }),
});
}}
>
<ProductReviews />
</Modal>
)}
</div>
);
}
五、最佳实践建议
1. 路由组织
- 使用文件路由模式
- 按功能模块组织文件
- 保持路由结构清晰
2. 性能优化
- 启用自动代码分割
- 使用懒加载
- 优化数据加载策略
3. 类型安全
- 定义完整的类型
- 使用
searchSchema验证参数 - 利用 TypeScript 的类型推断
4. 状态管理
- 使用 URL 参数管理状态
- 合理使用 loader 和 action
- 处理好路由间的数据传递
六、TanStack Router 的优势
-
类型安全
- 完整的 TypeScript 支持
- 路由参数验证
- 编译时错误检查
-
性能优化
- 自动代码分割
- 智能缓存
- 并行数据加载
-
开发体验
- 声明式路由配置
- 简洁的 API
- 强大的开发工具
-
功能特性
- 内置搜索参数处理
- 路由守卫
- 错误边界处理
七、参考
结语 & 加学习群 & 摸鱼群
感谢你读到这里!我是 hyy:
- 🚀 一个有着丰富经验的商业型 TypeScript 全栈开发者
- 💼 曾在小型外企、大型外包公司、创业公司等多个领域积累经验
- 📝 掘金技术社区的活跃作者
- 🎵 一个热爱电子音乐的 EDM 爱好者
- 🎮 一个热衷于电子游戏的玩家
- 🌟 一个乐于分享和交流的技术人
如果你也对前端开发充满热情,或者想要:
- 一起学习和进步
- 交流面试经验
- 分享职业发展心得
- 讨论金融、音乐、篮球、历史等兴趣爱好
点这个,有10000多名前端小伙伴在等着一起学习哦 --> 摸鱼沸点