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: 路由守卫组件。
- LoadingFallback/: 懒加载时的后备UI。
- 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>
);
}
路由类型解析
-
普通路由:如
<Route path="/" element={<Home />} />和<Route path="/about" element={<About />} />。这些是静态路径,直接映射到组件。 -
动态路由:使用
: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动态渲染内容。 -
嵌套路由:Product页面使用
<Outlet />作为子路由占位符:import { Outlet } from 'react-router-dom'; export default function Product() { return ( <> <h1>产品列表</h1> <Outlet /> </> ); }子路由如
/product/:productId会渲染在Outlet位置,实现布局复用。 -
鉴权路由(路由守卫):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状态。
-
重定向路由:
<Route path="/old-path" element={<Navigate replace to="/new-path" />} />。replace替换历史记录,避免回退到旧路径。 -
通配路由:
<Route path="*" element={<NotFound />} />捕获所有未匹配路径。
懒加载与Suspense
所有组件使用lazy(() => import('path'))延迟加载,仅在路由匹配时导入,优化首屏加载。Suspense包裹Routes,提供fallback UI(LoadingFallback),防止白屏。
NotFound组件中使用useNavigate和useEffect实现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应用的场景。