React Router 进阶实战:从基础路由到鉴权 + 懒加载完整演示

12 阅读6分钟

React Router 进阶实战:从基础路由到鉴权 + 懒加载完整演示

引言

我最近基于Vite脚手架初始化了一个名为“router-demo”的React项目,使用JavaScript作为主要语言。这个项目旨在探索和实践React Router DOM的各种功能,包括普通路由、动态路由、嵌套路由、鉴权路由、重定向路由以及组件懒加载等。项目结合了React的核心概念,如组件化开发、Hooks的使用(例如useNavigate、useParams、useEffect),并引入了Suspense和lazy来优化性能。

这个笔记将详细记录项目的初始化过程、目录结构、核心代码实现、路由机制的原理以及一些最佳实践。

项目的主要目标是演示前端路由如何实现页面切换,而不依赖后端路由,从而提升用户体验,避免传统多页应用的页面刷新白屏问题。整个项目使用BrowserRouter作为路由容器,利用HTML5 History API实现干净的URL路径。

项目初始化与基础配置

首先,使用Vite脚手架快速初始化React项目。命令如下:

npm init vite

我们可以选择react + JavaScript 作为我们这次项目的开发框架和语言。这会生成一个基本的React项目结构。Vite的优势在于构建速度快、热模块替换(HMR)高效,适合现代React开发。

安装React Router DOM:

npm install react-router-dom

在main.jsx中,我们使用ReactDOM.render将App组件挂载到DOM上。App.jsx作为入口组件,包裹了路由容器:

import { BrowserRouter as Router } from 'react-router-dom';
import Navigation from './components/Navigation';
import RouterConfig from './router';

export default function App() {
  return (
    <Router>
      <Navigation />
      <RouterConfig />
    </Router>
  );
}

这里,BrowserRouter被别名为Router,提高代码可读性。Navigation组件提供导航链接,RouterConfig则定义所有路由规则。

项目目录结构

基于提供的项目截图和代码,目录结构如下:

  • src/: 源代码主目录。
    • assets/: 图片、字体等静态资产。
    • components/: 可复用组件。
      • LoadingFallback/: 懒加载时的后备UI。
        • index.jsx: 组件逻辑。
        • index.module.css: 样式。
      • Navigation.jsx: 导航栏组件。
      • ProtectRoute.jsx: 路由守卫组件。
    • pages/: 页面级组件。
      • About.jsx: 关于页面。
      • Home.jsx: 首页。
      • Login.jsx: 登录页面。
      • NewPath.jsx: 新路径页面。
      • NotFound.jsx: 404页面。
      • Pay.jsx: 支付页面。
      • UserProfile.jsx: 用户资料页面。
      • product/: 产品相关子页面(嵌套路由)。
        • index.jsx: 产品列表页面。
        • NewProduct.jsx: 新产品页面。
        • ProductDetail.jsx: 产品详情页面。
    • router/: 路由配置。
      • index.jsx: 路由定义文件(RouterConfig)。

这个结构遵循React的最佳实践:页面组件放在pages下,公共组件在components下,路由配置独立在router下。这样的组织便于维护大型项目。

路由配置详解(RouterConfig.jsx)

路由是项目的核心,使用react-router-dom的Routes、Route、lazy和Suspense实现。完整代码如下:

import { lazy, Suspense } from 'react';
import { Navigate, Routes, Route } from 'react-router-dom';
import LoadingFallback from '../components/LoadingFallback';

const Home = lazy(() => import('../pages/Home'));
const About = lazy(() => import('../pages/About'));
const UserProfile = lazy(() => import('../pages/UserProfile'));
const Product = lazy(() => import('../pages/product'));
const ProductDetail = lazy(() => import('../pages/product/ProductDetail'));
const NewProduct = lazy(() => import('../pages/product/NewProduct'));
const Login = lazy(() => import('../pages/Login'));
const ProtectRoute = lazy(() => import('../components/ProtectRoute'));
const Pay = lazy(() => import('../pages/Pay'));
const NotFound = lazy(() => import('../pages/NotFound'));
const NewPath = lazy(() => import('../pages/NewPath'));

export default function RouterConfig() {
  return (
    <Suspense fallback={<LoadingFallback />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/user/:id" element={<UserProfile />} />
        <Route path="/product" element={<Product />}>
          <Route path=":productId" element={<ProductDetail />} />
          <Route path="new" element={<NewProduct />} />
        </Route>
        <Route path="/login" element={<Login />} />
        <Route path="/old-path" element={<Navigate replace to="/new-path" />} />
        <Route path="/new-path" element={<NewPath />} />
        <Route path="/pay" element={<ProtectRoute><Pay /></ProtectRoute>} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </Suspense>
  );
}

路由类型解析

  1. 普通路由:如<Route path="/" element={<Home />} /><Route path="/about" element={<About />} />。这些是静态路径,直接映射到组件。

  2. 动态路由:使用:param语法,例如<Route path="/user/:id" element={<UserProfile />} />。在UserProfile组件中,通过useParams()获取参数:

    import { useParams } from 'react-router-dom';
    export default function UserProfile() {
      const { id } = useParams();
      return <>UserProfile {id}</>;
    }
    

    这允许URL如/user/123动态渲染内容。

  3. 嵌套路由:Product页面使用<Outlet />作为子路由占位符:

    import { Outlet } from 'react-router-dom';
    export default function Product() {
      return (
        <>
          <h1>产品列表</h1>
          <Outlet />
        </>
      );
    }
    

    子路由如/product/:productId会渲染在Outlet位置,实现布局复用。

  4. 鉴权路由(路由守卫):Pay页面包裹在ProtectRoute中:

    import { Navigate } from 'react-router-dom';
    export default function ProtectRoute({ children }) {
      const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
      if (!isLoggedIn) {
        return <Navigate to="/login" />;
      }
      return children;
    }
    

    这检查localStorage中的登录状态,未登录则重定向到登录页。实际项目中,可集成JWT或Redux状态。

  5. 重定向路由<Route path="/old-path" element={<Navigate replace to="/new-path" />} />replace替换历史记录,避免回退到旧路径。

  6. 通配路由<Route path="*" element={<NotFound />} />捕获所有未匹配路径。

懒加载与Suspense

所有组件使用lazy(() => import('path'))延迟加载,仅在路由匹配时导入,优化首屏加载。Suspense包裹Routes,提供fallback UI(LoadingFallback),防止白屏。

NotFound组件中使用useNavigateuseEffect实现6秒后自动跳转首页:

import { useNavigate } from 'react-router-dom';
import { useEffect } from 'react';
const NotFound = () => {
  let navigate = useNavigate();
  useEffect(() => {
    setTimeout(() => {
      navigate('/');
    }, 6000);
  }, []);
  return <>NotFound</>;
};

导航组件(Navigation.jsx)

导航使用Link组件实现无刷新跳转,并通过useResolvedPath和useMatch判断活跃状态添加class:

import { Link, useResolvedPath, useMatch } from 'react-router-dom';

export default function Navigation() {
  const isActive = (to) => {
    const resolvedPath = useResolvedPath(to);
    const match = useMatch({ path: resolvedPath.pathname, end: true });
    return match ? 'active' : '';
  };
  return (
    <nav>
      <ul>
        <li><Link to="/" className={isActive('/')}>Home</Link></li>
        <li><Link to="/about" className={isActive('/about')}>About</Link></li>
        <li><Link to="/product">Product</Link></li>
        <li><Link to="/product/new">Product New</Link></li>
        <li><Link to="/product/123">Product Detail</Link></li>
        <li><Link to="/pay">Pay</Link></li>
        <li><Link to="/old-path">old-path</Link></li>
      </ul>
    </nav>
  );
}

这确保当前路由链接高亮,提升UX。

加载后备组件(LoadingFallback)

懒加载时的UI,使用CSS动画实现旋转加载器:

import styles from './index.module.css';
export default function LoadingFallback() {
  return (
    <div className={styles.container}>
      <div className={styles.spinner}>
        <div className={styles.circle}></div>
        <div className={`${styles.circle} ${styles.inner}`}></div>
      </div>
      <p className={styles.text}>Loading...</p>
    </div>
  );
}

CSS关键帧动画:

/* 省略部分,详见原代码 */
@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

这提供平滑的加载体验。

页面组件详解

  • Home.jsx & About.jsx: 简单渲染文本,使用console.log调试渲染。

  • Login.jsx: 基本登录UI,可扩展表单。

  • NewPath.jsx: 重定向目标页面。

  • Pay.jsx: 鉴权保护的支付页面。

  • Product子页面: Product作为父,NewProduct和ProductDetail作为子,使用useParams获取productId。

扩展到AI全栈项目

基于提供的Markdown,这个router-demo可以作为前端部分的起点,扩展到一个完整的AI全栈项目。

技能点

React开发全家桶
  • React + TypeScript/JS: 当前项目使用JS,但可升级到TS提升类型安全。
  • react-router-dom: 如上详述,前端路由实现SPA。
  • Zustand: 轻量状态管理,可替换localStorage存储登录状态。
  • Axios: 用于API请求,例如登录时调用后端。
后端
  • Node + TS + NestJS: 企业级框架,构建RESTful API。
  • PostgreSQL: 关系型数据库,存储用户、产品数据。
  • Redis: 缓存session或热门数据。
AI集成
  • LangChain: 构建AI链路,如聊天机器人。
  • Coze/n8n: 工作流自动化。
  • LLM: 如OpenAI模型集成。
  • Trae/Cursor: 可能指Trace或Cursor工具,辅助调试。

项目安排

  • Frontend: 当前router-demo,添加Zustand和Axios。
  • Backend: NestJS服务,连接PSQL和Redis。
  • AI Server: 独立服务处理LLM调用。
  • Admin: 后台管理系统,使用React Admin框架。

Git操作

  • 初始化:git init
  • 提交:每个模块完成后,如git commit -m "feat: add router config"
  • 信息准确:描述变更类型(feat、fix等)。

React Router原理

早期前端无路由,由后端控制。现在前后端分离,React Router使用HTML5 History或Hash模式。BrowserRouter兼容好,但需服务器配置支持SPA(重定向到index.html)。

性能优化:懒加载减少bundle大小,Suspense处理异步。

单页应用 vs 多页

SPA:路由变更时,前端事件驱动组件渲染,无全页刷新。历史栈管理前进后退。

最佳实践与总结

  • 性能: 始终使用lazy加载大组件。
  • 安全性: 鉴权使用更 robust 的方式,如Context或Redux。
  • 测试: 添加React Testing Library测试路由。
  • 扩展: 集成i18n、主题切换。
  • 调试: 使用React DevTools查看组件树。

这个项目演示了React Router的强大,适用于从简单SPA到复杂全栈AI应用的场景。