从前的前端
在很久之前的页面上,我们点击后,浏览器会有白屏一段时间,页面的跳转是后端干的事
但是后来,前端致力于用户的体验,路由渐渐的落到了前端的身上。下面我会根据一个todos小应用来说明路由的作用
两种路由形式
在 React Router 中,HashRouter 和 BrowserRouter 是两种常见的路由实现方式。
HashRouter
HashRouter 的 URL 会带一个 #,比如 /#/about
看起来没那么清爽,但它非常全能,不需要后端配合,兼容性极好,连老旧的 IE 浏览器都能跑
特别适合部署在 GitHub Pages 这类无法自定义服务器配置的静态托管环境。
这个 '#' 看起来会不会有点'丑'?
BroswerRouter
而 BrowserRouter 使用的是标准的路径形式,如 /about
和传统后端路由一致,URL 更干净、可读性更强,也更利于 SEO
但它依赖 HTML5 的 History API
IE11 及更早版本不支持(不过如今主流浏览器都已全面支持)
配合路由的懒加载能力,比如用 React.lazy + Suspense,我们可以做到访问 / 时只加载 Home 组件,访问 /about 时才加载 About 组件,其他页面代码按需加载 ,既提升了首屏性能,又优化了用户体验
因此,在现代项目中,只要能控制服务器(或使用 Vercel、Netlify 等智能托管平台),优先选择
BrowserRouter;若环境受限或需极致兼容,再退而使用HashRouter。
懒加载
懒加载,就是用到再加载。
在 React 项目里,如果不做处理,所有页面的代码(比如首页、个人中心、商品详情等)都会被打包进一个大文件,用户一打开网站就得先下载这个大包,哪怕他只看首页。
而用了 React.lazy 配合 import() 之后,每个页面会被拆成独立的小文件,只有当用户真正访问那个路由时,浏览器才去请求对应的代码。比如我的todos
js
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'));
懒加载后,首屏加载快多了,尤其对网速慢或者用手机的用户特别友好。
配合 <Suspense> 包一层,还能在加载时显示个转圈或提示,避免白屏卡顿。
不过要注意,懒加载只支持默认导出(export default),而且一般用在路由级别最合适
太细了 比如一个页面里每个按钮、每个小组件都单独懒加载。
结果:用户打开一个页面,浏览器要发 10 个、20 个小请求去加载碎片代码,反而更慢(HTTP 请求本身有开销)。反而会发太多小请求
太粗比如把所有页面打包成两三个大块,那首屏还是得下很多用不到的代码,
失去了懒加载的意义又起不到优化效果。总的来说,这是现代 React 应用提升性能、优化体验的一个基本操作。
路由的种类
在一个React项目中,路由一般放在router文件夹目录下
下面我们用实战案例来讲解以下集中路由:
普通路由:
路径是固定的,不包含参数。
jsx
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
我们可以看到当用户访问 /about时,就显示 About 组件。
这里的路径是写死的,一一对应,最简单直接。
动态路由(Dynamic Route)
路径中包含变量参数 ,用 :参数名 表示。
jsx
<Route path="/user/:id" element={<UserProfile />} />
<Route path=":productId" element={<ProductDetail />}/>
在我们访问/user/123时 :id的值就是123
我们可以在组件UserProfile或ProductDetail里 ,可以通过useParams()拿到参数

嵌套路由(Nested Routes) + Outlet
父路由包含子路由,共享布局,子路由内容通过 Outlet 渲染。
jsx
<Route path="/products" element={<Product/>}>
<Route path=":productId" element={<ProductDetail />}/>
<Route path="new" element={<NewProduct />}/>
</Route>
比如说我们访问 /product 会显示Product

访问/product/123 仍然显示Product

嵌套路由的好处:复用布局(比如侧边栏、导航栏),避免重复写结构。
鉴权路由(Protected Route)
jsx
<Route path="/pay" element={
<ProtectRoute>
<Pay />
</ProtectRoute>
}>
</Route>
访问受保护页面(如 /pay)
React 渲染
jsx
<ProtectRoute><Pay /></ProtectRoute>
而在ProtectRoute里写了以下逻辑:
jsx
const isLoggedIn = localStorage.getItem('isLogin') === 'true';
if (!isLoggedIn) {
return <Navigate to="/login"/>
}
return (
<>{children}</>
)
ProtectRoute 立刻读取 localStorage.getItem('isLogin')
如果值是 'true' → 显示 <Pay />
如果是 null 或 'false' → 跳转到 /login 这个逻辑实现了"未登录跳转到登录页,已登录就显示内容"的核心功能
但是我们必须知道,这个鉴权只能防止用户的误操作,安全系数并不是很高,即使前端用了更先进的方法,这个不是这篇文章要讲的了。
重定向路由(Redirect / Navigate)
把用户从一个旧路径自动跳转到新路径。
jsx
<Route path="/old-path" element={<Navigate replace to="/new-path"/>}/>
<Route path="/new-path" element={<NewPath />}/>
我们在点击 <Navigate replace to="/new-path" /> 时
浏览器不会把旧路径 /old-path 留在历史记录中,而是直接用新路径 /new-path 替换当前记录。
这意味着用户点击"返回"按钮时,不会回到 /old-path,而是跳转到更早的页面。
这种机制基于 History API 的 replaceState,常用于页面改名、登录跳转或首页重定向等场景,避免无效或冗余的历史条目,让浏览器的前进/后退行为更自然、更符合预期。

总结
我们从前端接管路由开始,页面跳转不再依赖后端刷新,而是通过 React Router 实现流畅的无刷新导航。
配合懒加载,应用按需加载页面代码,显著提升了首屏速度 ;普通路由、动态路由、嵌套路由等灵活组合,满足各种场景需求;鉴权路由拦截未登录访问,重定向路由处理路径变更
这些能力共同构建了现代 React 应用高效、友好且结构清晰的导航体验。