今天我们把一个商城后台的路由需求拆解成 5 个场景,让你一次看懂 v6 的现代用法。

🎯 场景需求

  • 登录页、仪表盘、商品管理、订单管理。
  • 仪表盘下有多个子路由:概览页、报表、设置。
  • 部分页面需要权限控制。
  • 需要懒加载和“加载中”状态。

🧱 基础配置:createBrowserRouter

import {
  createBrowserRouter,
  RouterProvider,
} from 'react-router-dom';

const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />, 
    errorElement: <ErrorPage />, // v6 的错误边界
    children: [
      {
        index: true,
        lazy: () => import('./pages/Dashboard'),
      },
      {
        path: 'products',
        loader: fetchProducts,
        element: <Products />,
      },
      {
        path: 'orders',
        loader: fetchOrders,
        element: <Orders />,
      },
      {
        path: 'settings',
        element: <Settings />,
      },
    ],
  },
  {
    path: '/login',
    element: <Login />,
  },
]);

function App() {
  return <RouterProvider router={router} />;
}
  • RouterProvider 是 v6.4+ 的新写法。
  • lazy 支持路由级别懒加载,自动处理 Suspense。
  • loader 用来在渲染前获取数据。

⚡ 路由懒加载与代码分割

lazy 组件形式:

// pages/Dashboard.jsx
export async function loader() {
  const stats = await fetch('/api/stats').then((res) => res.json());
  return { stats };
}

export default function Dashboard() {
  const { stats } = useLoaderData();
  return <div>欢迎回来,今日数据:{stats.users}</div>;
}
  • useLoaderData 获取 loader 返回的数据。
  • 自动处理加载状态,需要配合 <React.Suspense fallback={...}>

🔐 路由守卫:loader + redirect

export async function protectedLoader({ request }) {
  const token = localStorage.getItem('token');
  if (!token) {
    throw redirect('/login');
  }
  const user = await fetchUser(token);
  return { user };
}

const router = createBrowserRouter([
  {
    path: '/',
    loader: protectedLoader,
    element: <Layout />,
    children: [/* ... */],
  },
]);
  • 如果没登录,redirect 会终止后续渲染。
  • Loader 可以返回数据给子组件使用。

🔄 动态路由与 useParams

{
  path: 'products/:id',
  loader: async ({ params }) => {
    return fetch(`/api/products/${params.id}`).then((res) => res.json());
  },
  element: <ProductDetail />,
}

function ProductDetail() {
  const product = useLoaderData();
  return <div>{product.name}</div>;
}
  • 不需要再手动调用 useEffect 请求,Loader 自动搞定。

🧩 Outlet 与嵌套路由

function Layout() {
  return (
    <div>
      <Sidebar />
      <main>
        <Suspense fallback={<PageSkeleton />}>
          <Outlet />
        </Suspense>
      </main>
    </div>
  );
}
  • Outlet 是子路由的出口。
  • Outlet 外层加 Suspense 可以统一管理加载状态。

🧯 常见坑

现象 解决
<Routes> 内嵌 <Route> 的旧写法报错 v6.4+ 改为 RouterProvider 升级到 createBrowserRouter 写法
loader 中请求失败 抛错误,自动跳到 errorElement 在 errorElement 中用 useRouteError 拿错误
路由懒加载闪烁 缺 Suspense fallback 外层加 <Suspense fallback={<Loading />}>
SSR 支持 需要搭配 Remix 或 Next.js App Router 使用 StaticRouter + hydrateRoot

⚖️ v5 vs v6 对比

特性 v5 v6
路由定义 <Switch><Route/></Switch> createBrowserRouter<Routes><Route/></Routes>
精确匹配 exact 属性 默认精确匹配
嵌套路由 需要手动传 props Outlet 自动渲染子路由
动态加载 需要 Suspense 手动处理 lazy 内置支持
数据加载 useEffect 手写 loader/action 内置

🏁 小练习

  1. 把你老项目的 Router v5 改写成 v6,记录坑点。
  2. 给某个路由添加 errorElement,模拟接口报错。
  3. 写一个 Loader + Action 搭配的案例(例如表单提交)。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐