写在前面:上篇讲了 React Router 的入门三件套(路由选型、Link、Routes/Route 配置),这篇是进阶篇------也是"更实用"的部分。老师说,真实项目里的路由不只是"配置一下路径"那么简单:页面太多要懒加载 (不然首页加载卡死)、用户没登录要拦截 (鉴权路由)、老链接要重定向 、访问不存在的路径要显示 404。这篇内容满满,都是干货,建议收藏。
一、路由懒加载:性能优化的"必修课"
1.1 问题:全部页面一次性加载
如果一个项目有几十个页面,全部用静态 import 加载:
jsx
import Home from './pages/Home';
import About from './pages/About';
import UserProfile from './pages/UserProfile';
// ...几十个页面
问题:用户访问首页时,几十个页面全部下载执行------首页加载速度慢得离谱。
老师说:
"首页/页面加载速度------非当前路由页面不加载。性能优化。"
1.2 解决方案:lazy + Suspense
jsx
import { lazy, Suspense } from 'react';
// 路由懒加载:点击时才加载对应页面
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const UserProfile = lazy(() => import('./pages/UserProfile'));
const Login = lazy(() => import('./pages/Login'));
const Pay = lazy(() => import('./pages/Pay'));
lazy(() => import(...)) 是动态导入------页面组件只有在需要渲染时才会被加载。
还需要用 Suspense 包裹,加载过程中显示 fallback:
jsx
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Navigation />
<div id="container">
<Routes>
{/* 路由配置 */}
</Routes>
</div>
</Suspense>
</Router>
Suspense 的 fallback 是加载过程中的"占位内容"------页面在下载时,先显示"Loading..."。
| 对比 | 静态 import | 懒加载(lazy) |
|---|---|---|
| 加载时机 | 项目启动时全部加载 | 访问到该路由时才加载 |
| 首页速度 | 慢(所有页面都下载) | 快(只下载首页) |
| 代码分割 | 无 | 自动按路由拆分 |
二、重定向:Navigate 组件
2.1 场景
老师说:
"有个活动
/game100 万,/result活动结束了;/home首页,重定向到/。"
旧链接失效了、活动结束了、地址换新了------都需要"重定向",把用户送到新地址。
2.2 实现
jsx
{/* 老路径跳转到新路径 */}
<Route path="old-path" element={
<Navigate replace to="/new-path" />
} />
<Navigate replace to="/new-path" /> 是一个"自动跳转"的组件:
to:跳转目标。replace:替换当前历史记录(用户按"返回"不会回到旧页面)。
这就像你在前台贴了一张公告:"我们搬家了,新地址在这边。"
三、404 页面:最后的"兜底"
3.1 问题
用户输入了一个不存在的路径(比如 /abc),会发生什么?
答案是:什么都不显示------空白页。 这是很差的体验。
3.2 解决方案:贪婪匹配
jsx
{/* "*" 贪婪匹配所有,最后 404 兜底 */}
<Route path="*" element={<NotFound />} />
老师说:
"
*贪婪匹配所有,最后 404 兜底。"
path="*" 会匹配所有没有被其他路由匹配到的路径。
关键:这个 Route 必须放在最后! 因为 Routes 是按顺序匹配的,前面的路径都试过了都不行,才会落到 * 上。
bash
/ → Home
/about → About
...(前面的路由优先匹配)
/abc → 前面都不匹配 → * 兜底 → NotFound
四、鉴权路由:ProtectRoute 守卫
4.1 什么是鉴权路由?
场景:支付页面必须登录才能访问。 未登录用户直接访问 /pay,应该被"拦下来"送到登录页。
老师说:
"HTTP 无状态的。有状态?① 请求头 token Authorization ② Cookie 小饼干 ③ localStorage 存储 login 状态。"
这里我们用 localStorage 存储登录状态。
4.2 ProtectRoute 实现
看 ProtectRoute.jsx:
jsx
import { Navigate } from 'react-router-dom';
const ProtectRoute = ({ children }) => {
// 拦截请求,鉴权
// HTML5 本地存储,域名的沙盒
const isLogin = localStorage.getItem('isLogin') === 'true';
if (!isLogin) {
// 如果未登录,跳转到登录页
// 路由设置 state 状态对象,记录从哪里来
return <Navigate to="/login" replace state={{ from: location.pathname }} />
}
return (
<>
{children}
</>
);
};
export default ProtectRoute;
ProtectRoute 是一个"门禁保安":
bash
用户访问 /pay
↓
ProtectRoute 检查 localStorage.getItem('isLogin')
↓
未登录 → <Navigate to="/login"> 送回登录页
已登录 → 渲染 {children}(放行)
关键细节:state={{ from: location.pathname }} ------记录用户"从哪里来"。登录成功后,可以跳回原页面(比如用户想访问 /pay,登录后直接送回 /pay)。
4.3 在路由中如何使用
jsx
<Route path="/pay" element={
// 门禁保安
// Pay 是要进的房间
// children 用来定制化组件
<ProtectRoute>
{/* children */}
<Pay />
</ProtectRoute>
} />
<Pay /> 被作为 children 传给了 <ProtectRoute>。 登录了,children 被渲染;没登录,直接送去登录页。
五、children:组件的"定制化插槽"
5.1 children 是什么?
老师说:
"组件内部的子组件------
props.children拿到组件声明的内部所有的子节点。"
jsx
<ProtectRoute>
<Pay /> {/* 这就是 children */}
</ProtectRoute>
在 ProtectRoute 里,props.children 就是 <Pay />。
5.2 children 的经典场景:弹窗(Modal)
老师说:
"Model 弹窗组件,mask 蒙层,窗体头部、尾部、主体部分 children 传入。定制性。"
jsx
const Modal = ({ children }) => {
return (
<div className="mask"> {/* 蒙层 */}
<div className="window">
<header>标题</header>
<div className="body">
{children} {/* 主体内容由调用者定制 */}
</div>
<footer>按钮区</footer>
</div>
</div>
);
};
// 使用:想放什么内容就放什么
<Modal>
<p>确认要删除吗?</p>
</Modal>
<Modal>
<form>...</form>
</Modal>
同一个 Modal 组件,通过 children 可以装任何内容------这就是"定制性"。
六、完整的路由配置(总结)
看 App.jsx 的完整配置:
jsx
<Routes>
{/* 基本配置 */}
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* 动态路由 */}
<Route path="/user/:id" element={<UserProfile />} />
{/* 嵌套路由 */}
<Route path="/products" element={<Products />}>
<Route path=":productId" element={<ProductDetail />} />
<Route path="new" element={<NewProduct />} />
</Route>
{/* 重定向 */}
<Route path="old-path" element={<Navigate replace to="/new-path" />} />
<Route path="/login" element={<Login />} />
{/* 鉴权路由 */}
<Route path="/pay" element={
<ProtectRoute>
<Pay />
</ProtectRoute>
} />
{/* 404 兜底 */}
<Route path="*" element={<NotFound />} />
</Routes>
一个完整项目的路由,通常包含:基本配置 + 动态路由 + 嵌套路由 + 重定向 + 鉴权 + 404。六种玩法,缺一不可。
七、总结(下篇)
| 概念 | 说明 |
|---|---|
| 懒加载 | lazy(() => import()) + Suspense,按需加载页面 |
| fallback | 加载过程中的占位内容 |
| Navigate | 重定向组件,to + replace |
| 404 兜底 | path="*" 贪婪匹配所有未匹配路径 |
| 鉴权路由 | ProtectRoute 检查登录状态,未登录送去登录页 |
| children | 组件内部子节点,实现"定制化插槽" |
| state 传参 | state={{ from: location.pathname }} 记录来源 |
路由懒加载优化性能,重定向和 404 兜底体验,鉴权路由保安全,children 定制化组件------这套组合拳,就是真实项目路由的完整形态。
写在最后
上下两篇加起来,我把 React Router 的完整知识体系梳理完了。从路由选型、Link 导航、基本配置,到动态路由、嵌套路由、懒加载、重定向、404、鉴权......虽然内容很多,但核心思想就一条:URL 和资源一一对应,页面无刷新切换。
下次面试官问你:"React Router 怎么做鉴权路由?"
你可以淡定地说:
"React Router 做鉴权路由的核心是封装一个 ProtectRoute 守卫组件。它接收 children,在渲染前检查登录状态------比如 localStorage.getItem('isLogin') === 'true'。如果未登录,返回 <Navigate to="/login" replace state={{ from: location.pathname }} /> 跳转到登录页,并通过 state 记录用户来源路径,登录成功后可以跳回原页面;如果已登录,直接渲染 children。使用时把需要保护的路由包起来:<Route path="/pay" element={<ProtectRoute><Pay /></ProtectRoute>} />。另外配合 lazy 懒加载优化性能、path="*" 做 404 兜底、Navigate 做重定向,就是完整的路由方案。"
然后看着面试官满意的表情,心里默念:这波,又稳了。
本文所有代码示例均来自课堂学习资料,真实可运行。