第四章、路由配置

路由配置

一、安装依赖

bash 复制代码
npm install react-router-dom

二、推荐目录结构

bash 复制代码
src/
├── router/
│   └── index.tsx             # 路由配置文件
├── pages/
│   ├── Home.tsx
│   ├── About/
│   │   ├── index.tsx          
│   │   ├── LanguageI18n.tsx        
│   │   └── ThemeSwitcher.tsx           
│   ├── User/
│   │   ├── UserList.tsx
│   │   └── UserDetail.tsx
├── layout/
│   └── MainLayout.tsx        # 公共布局
├── App.tsx
└── index.tsx

三、基础路由配置(src/router/index.tsx

tsx 复制代码
import React from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";

import MainLayout from "@/layout/MainLayout";
import Home from "@/pages/Home";
import About from "@/pages/About/About";
import Company from "@/pages/About/LanguageI18n";
import Team from "@/pages/About/ThemeSwitcher";
import UserList from "@/pages/User/UserList";
import UserDetail from "@/pages/User/UserDetail";

const router = createBrowserRouter([
  {
    path: "/",
    element: <MainLayout />,// 公共布局
    children: [
      { index: true, element: <Home /> },// 默认路由
      {
        path: "about",
        element: <About />,
        children: [
          { index: true, element: <Navigate to="languageI18n" replace /> },//设置默认子路由
          { path: "languageI18n", element: <LanguageI18n /> },
          { path: "themeSwitcher", element: <ThemeSwitcher /> },
        ],
      },
      { path: "users", element: <UserList /> },
      { path: "users/:id", element: <UserDetail /> },// 动态参数
    ],
  },
]);

export default function AppRouter() {
  return <RouterProvider router={router} />;
}

四、在入口文件中加载路由

src/index.tsx

tsx 复制代码
import React from "react";
import { createRoot } from "react-dom/client";
import "./styles/globals.css";
import "./index.css";
import { ThemeProvider } from "./context/ThemeContext";
import "./i18n";
import AppRouter from "./router";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
      <ThemeProvider>
        <AppRouter />
     </ThemeProvider>
  </React.StrictMode>
);

五、导航与跳转示例

src/pages/Home.tsx

tsx 复制代码
import React from "react";
import { Link, useNavigate } from "react-router-dom";

const Home: React.FC = () => {
  const navigate = useNavigate();

  const goToUser = () => {
    navigate("/users/42");
  };

  return (
    <div>
      <h1>🏠 Home</h1>
      <p>欢迎来到首页</p>
      <Link to="/about">去关于页</Link>
      <br />
      <button onClick={goToUser}>跳转到用户详情</button>
    </div>
  );
};

export default Home;

六、动态路由参数获取

src/pages/User/UserDetail.tsx

tsx 复制代码
import React from "react";
import { useParams } from "react-router-dom";

const UserDetail: React.FC = () => {
  const { id } = useParams<{ id: string }>();

  return (
    <div>
      <h2>用户详情页</h2>
      <p>当前用户ID: {id}</p>
    </div>
  );
};

export default UserDetail;

七、嵌套路由(src/pages/About/index.tsx

index.tsx 作为父级页面,需要提供导航入口,并包含一个 <Outlet /> 来渲染子页面内容。

tsx 复制代码
import React from "react";
import { Link, Outlet } from "react-router-dom";

const About: React.FC = () => {
  return (
    <div>
      <h1>关于</h1>
      <nav style={{ marginBottom: "1rem" }}>
          <Link to="languageI18n">国际化切换</Link> | <Link to="themeSwitcher">主题切换</Link>
      </nav>

      {/* 子路由出口 */}
      <Outlet />
    </div>
  );
};

export default About;

九、子页面

src/pages/About/LanguageI18n.tsx

tsx 复制代码
import LanguageSwitcher from "@/components/LanguageSwitcher";
import React from "react";
import { useTranslation } from "react-i18next";

const LanguageI18n: React.FC = () => {
  const { t } = useTranslation();
  return (
    <div className="p-8">
        <h1>{t("welcome")}</h1>
        <p>
            {t("language")}: {t("change_language")}
        </p>
        <LanguageSwitcher />
      </div>
  );
};

export default LanguageI18n;

src/pages/About/ThemeSwitcher.tsx

tsx 复制代码
import { useTheme } from "@/context/ThemeContext";
import React from "react";

const ThemeSwitcher: React.FC = () => {
  const { mode, theme, setMode, toggleTheme } = useTheme();
  return (
    <div style={{ padding: "2rem" }}>
      <h1>🌗 React 三种主题模式</h1>
      <p>当前模式:{mode}</p>
      <p>当前实际主题:{theme}</p>

      <div style={{ display: "flex", gap: "10px", marginBottom: "20px" }}>
        <button onClick={() => setMode("light")}>亮色模式</button>
        <button onClick={() => setMode("dark")}>暗色模式</button>
        <button onClick={() => setMode("system")}>跟随系统</button>
        <button onClick={toggleTheme}>手动切换主题</button>
      </div>
      <p>示例文字会根据主题自动变色。</p>
    </div>
  );
};

export default ThemeSwitcher;

十、路由懒加载(代码分割)

一、为什么要用懒加载?

推荐:React.lazy + Suspense

在开发中,React 项目文件越来越大,如果所有页面在首次加载时都被打包下载,会导致:

  • 首屏加载慢
  • 打包体积大
  • 用户等待时间长

解决方案: 按需加载 (代码分割)

只有当用户访问某个路由页面时,才异步加载该页面代码。

这就是 React.lazy + Suspense 的核心功能。

二、改造后的 src/router/index.tsx

在(src/router/index.tsx)中

tsx 复制代码
import React, { Suspense, lazy } from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";

// 使用 React.lazy 动态导入组件
const MainLayout = lazy(() => import("@/layout/MainLayout"));
const Home = lazy(() => import("@/pages/Home"));
const About = lazy(() => import("@/pages/About"));
const LanguageI18n = lazy(() => import("@/pages/About/languageI18n"));
const ThemeSwitcher = lazy(() => import("@/pages/About/ThemeSwitcher"));
const UserList = lazy(() => import("@/pages/User/UserList"));
const UserDetail = lazy(() => import("@/pages/User/UserDetail"));

const router = createBrowserRouter([
  {
    path: "/",
    element: <MainLayout />,// 公共布局
    children: [
      { index: true, element: <Home /> },// 默认路由
      {
        path: "about",
        element: <About />,
        children: [
          { index: true, element: <Navigate to="languageI18n" replace /> },//设置默认子路由
          { path: "languageI18n", element: <LanguageI18n /> },
          { path: "themeSwitcher", element: <ThemeSwitcher /> },
        ],
      },
      { path: "users", element: <UserList /> },
      { path: "users/:id", element: <UserDetail /> },// 动态参数
    ],
  },
]);

export default function AppRouter() {
  return <RouterProvider router={router} />;
}

三、全局 Suspense + Loading 动画

(1)Suspense的作用:

tsx 复制代码
<Suspense fallback={<div>加载中...</div>}>
  <About />
</Suspense>
  • Suspense 是一个占位组件
  • 当内部的 lazy 组件正在异步加载时,React 会渲染 fallback(占位内容);
  • 加载完成后,React 会自动替换为真实组件。
  • 这使得用户体验更加平滑(不会白屏)。

通俗理解:

"页面在加载时显示一段提示(比如'加载中...'),加载完再显示真正的页面。"

(2) 全局 Suspense + 优雅 Loading 动画 的懒加载版本。

bash 复制代码
src/
├── router/
│   └── index.tsx             # 路由配置
├── components/
│   └── Loading.tsx           # 全局加载动画
├── layout/
│   └── MainLayout.tsx
└── index.tsx

全局加载动画组件src/components/Loading.tsx

tsx 复制代码
import React from "react";

const Loading: React.FC = () => {
  return (
    <div
      style={{
        height: "100vh",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        flexDirection: "column",
        fontSize: "18px",
        color: "#555",
      }}
    >
      <div
        style={{
          width: 40,
          height: 40,
          border: "4px solid #ccc",
          borderTopColor: "#4f46e5",
          borderRadius: "50%",
          animation: "spin 1s linear infinite",
        }}
      ></div>
      <p style={{ marginTop: 10 }}>页面加载中...</p>

      <style>
        {`@keyframes spin { 
            from { transform: rotate(0deg); } 
            to { transform: rotate(360deg); } 
          }`}
      </style>
    </div>
  );
};

export default Loading;

src/router/index.tsx

tsx 复制代码
import Loading from "@/components/Loading";
//...

// 用 Suspense 包裹整个路由系统
const AppRouter: React.FC = () => {
  return (
    <Suspense fallback={<Loading />}>
      <RouterProvider router={router} />
    </Suspense>
  );
};

export default AppRouter;

全局 Suspense 的优势

✅ 只写一次 fallback,所有懒加载页面共用

✅ 不需要在每个 <Route><Suspense>

✅ 结构更清晰,可直接替换为统一的动画或骨架屏

十一、其他问题

1.别名使用

import MainLayout from "@/layout/MainLayout"; 时却报错。

这个问题通常是因为 TypeScript 编译器无法识别 Webpack 的别名设置。Webpack 在打包时会处理这些别名,但 TypeScript 在编译时也需要知道这些别名。需要同时在 Webpack 和 TypeScript 中配置别名。

修改 tsconfig.json

json 复制代码
{
  "compilerOptions": {
   ...
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

完善 Webpack 配置webpack.common.js

js 复制代码
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
    alias: {
      '@': path.resolve(__dirname, '../src'),
    },
  },

注意补充完后,重启一下

2.React Router DOM 最常用方法和组件表格

核心组件
组件 用途 示例 使用频率
BrowserRouter 应用根路由容器 <BrowserRouter><App /></BrowserRouter> ⭐⭐⭐⭐⭐
Routes 路由配置容器 <Routes><Route ... /></Routes> ⭐⭐⭐⭐⭐
Route 定义单个路由 <Route path="/" element={<Home />} /> ⭐⭐⭐⭐⭐
Link 声明式导航链接 <Link to="/about">关于</Link> ⭐⭐⭐⭐⭐
Outlet 嵌套路由渲染位置 <Outlet /> ⭐⭐⭐⭐
NavLink 带激活状态的链接 <NavLink to="/" className={({isActive}) => ...}> ⭐⭐⭐
常用 Hooks
Hook 用途 示例 使用频率
useNavigate 编程式导航 const navigate = useNavigate(); navigate('/path') ⭐⭐⭐⭐⭐
useParams 获取路由参数 const { id } = useParams(); ⭐⭐⭐⭐⭐
useLocation 获取当前位置信息 const location = useLocation(); ⭐⭐⭐⭐
useSearchParams 处理URL查询参数 const [params, setParams] = useSearchParams(); ⭐⭐⭐⭐
useRoutes 对象形式定义路由 const routes = useRoutes([...]); ⭐⭐⭐
导航组件对比
特性 Link NavLink useNavigate
类型 声明式 声明式 编程式
激活状态
使用场景 普通链接 导航菜单 事件处理、表单提交
参数获取对比
场景 使用 Hook 示例
路径参数 useParams /user/:idconst {id} = useParams()
查询参数 useSearchParams ?page=1const [params] = useSearchParams()
状态传递 useLocation navigate('/path', {state: {data}})
路由配置及菜单数据

(1)分清楚路由配置的数据和菜单导航的渲染数据

在菜单渲染数据里子菜单漏写父路径会导致404

(2)<Outlet />子路由出口

src/pages/About/index.tsx

MainLayout.tsx

首页、关于、用户等都是子路由

所以MainLayout.tsx是公共布局,主布局页面

相关推荐
吃杠碰小鸡15 小时前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone15 小时前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_090116 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农16 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king16 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳16 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵17 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星17 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_17 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝18 小时前
RBAC前端架构-01:项目初始化
前端·架构