前端开发必备:TanStack Router  完全指南 - 从入门到实践(2)

2,325 阅读3分钟

前言

大家好,我是 hyy,一个热爱技术分享的全栈工程师。

作为一个有着丰富经验的商业型 TypeScript 全栈开发者,我深知技术分享的重要性。我的座右铭是"深入浅出,知其然知其所以然",因为我始终相信,只有真正理解了基础,才能在技术的道路上走得更远。

在这篇文章中,我将带你深入浅出地了解 TanStack Router 这个强大的路由工具。无论你是初学者还是有经验的开发者,相信都能从中获得启发。

一、为什么选择 TanStack Router?

传统路由的痛点

  1. 类型安全问题

    • 路由参数类型不确定
    • 路由配置缺乏类型检查
    • 路由跳转参数容易出错
  2. 状态管理复杂

    • URL 参数与应用状态同步困难
    • 需要手动管理路由状态
    • 页面间数据传递不便
  3. 代码组织问题

    • 路由配置分散
    • 文件组织结构不清晰
    • 路由与组件耦合度高

二、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 的优势

  1. 类型安全

    • 完整的 TypeScript 支持
    • 路由参数验证
    • 编译时错误检查
  2. 性能优化

    • 自动代码分割
    • 智能缓存
    • 并行数据加载
  3. 开发体验

    • 声明式路由配置
    • 简洁的 API
    • 强大的开发工具
  4. 功能特性

    • 内置搜索参数处理
    • 路由守卫
    • 错误边界处理

七、参考

结语 & 加学习群 & 摸鱼群

感谢你读到这里!我是 hyy:

  • 🚀 一个有着丰富经验的商业型 TypeScript 全栈开发者
  • 💼 曾在小型外企、大型外包公司、创业公司等多个领域积累经验
  • 📝 掘金技术社区的活跃作者
  • 🎵 一个热爱电子音乐的 EDM 爱好者
  • 🎮 一个热衷于电子游戏的玩家
  • 🌟 一个乐于分享和交流的技术人

如果你也对前端开发充满热情,或者想要:

  • 一起学习和进步
  • 交流面试经验
  • 分享职业发展心得
  • 讨论金融、音乐、篮球、历史等兴趣爱好

点这个,有10000多名前端小伙伴在等着一起学习哦 --> 摸鱼沸点