普通视图

发现新文章,点击刷新页面。
昨天 — 2025年10月24日首页

第四章、路由配置

2025年10月24日 10:54

路由配置

一、安装依赖

npm install react-router-dom

二、推荐目录结构

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

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

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

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

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 /> 来渲染子页面内容。

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

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

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)中

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的作用:

<Suspense fallback={<div>加载中...</div>}>
  <About />
</Suspense>

  • Suspense 是一个占位组件
  • 当内部的 lazy 组件正在异步加载时,React 会渲染 fallback(占位内容);
  • 加载完成后,React 会自动替换为真实组件。
  • 这使得用户体验更加平滑(不会白屏)。

通俗理解:

“页面在加载时显示一段提示(比如‘加载中…’),加载完再显示真正的页面。”

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

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

全局加载动画组件src/components/Loading.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

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

{
  "compilerOptions": {
   ...
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

完善 Webpack 配置webpack.common.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/:id → const {id} = useParams()
查询参数 useSearchParams ?page=1 → const [params] = useSearchParams()
状态传递 useLocation navigate('/path', {state: {data}})
路由配置及菜单数据

(1)分清楚路由配置的数据和菜单导航的渲染数据 image.png 在菜单渲染数据里子菜单漏写父路径会导致404

image.png

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

src/pages/About/index.tsximage.png

image.png

MainLayout.tsx

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

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

昨天以前首页
❌
❌