前言
在上一期我们介绍了history和hash两种前端路由常用的模式,它们能够使得我们在不重载刷新页面的情况下,实现页面的更新,让用户体验又向上拔高了一个层级,让服务器也没有以前忙的找不着北了,既然它这么有用,我们总不能用纯生JS一直用吧?要知道现在的主流开发都是用Vue或React这种框架的,那么这些框架中有没有类似的功能呢?聪明如你当然知道了,react中也存在类似的功能,那么今天我们就来介绍一下react router!
什么是 React Router ?
所以什么是React Router呢?
React Router 是 React 生态中最流行的客户端路由(Client-Side Routing) 管理库,用于构建单页应用(SPA, Single Page Application) 。它允许我们在不刷新整个页面的情况下,动态切换不同的视图(组件),并保持URL与界面同步。
它的核心功能包括:
- URL 映射到组件:根据浏览器地址栏的路径,渲染对应的 React 组件。
- 导航控制:提供
<Link>和useNavigate等方式实现页面跳转。 - 动态路由:支持参数化路径(如
/users/:id),便于数据驱动视图。 - 嵌套路由:允许页面布局分层,提高代码复用性。
如果说history/hash让一般的Web项目有了让SPA展示多页内容的能力,那么React Router就是让React项目也有了这个能力。
React Router 安装
首先我们要知道它并不是react自带的,react router是一个第三方库,所以即使你初始化了一个react文件,你仍然需要在项目文件的根目录下安装这个库才能使用:
npm install react-router-dom
React Router 基本用法
接下来就是它的基础用法了:
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/users/:id" element={<UserProfile />} />
</Routes>
</Router>
);
}
我们来逐一介绍BrowserRouter, Routes, Route.
BrowserRouter
BrowserRouter可以提供基于HTML5 History API的路由容器:
- 它可以包裹整个应用,使 React Router 的功能(如导航、路由匹配)生效。
- 它使用 HTML5 的
pushState和popState来管理 URL,确保页面无刷新跳转。 - 当 URL 变化时,自动触发重新渲染匹配的组件。
也就是说,想让下面的路由都能享受到便捷的功能,就都得加入BrowserRouter的麾下。它就像个基石一样,没有它就是不行。
Routes
Routes是一个规则匹配容器,它会包裹一组 <Route>,负责根据当前 URL 选择最优匹配的路由。
简而言之,当你在这一组<Route>中输入url来回切换时,是Routes在识别并帮你找到对应的<Route>展现在页面上。
当然,<Route>必须全部被包裹在Routes中才能享受这个功能。
Route
Route可以定义路由规则,将 URL 路径(path)与 要渲染的组件(element)关联。
-
Route支持:- 静态路径(如
/about) - 动态路径参数(如
/users/:id) - 嵌套路由(通过父路由的
<Outlet>渲染子路由) - 默认路由(
index属性)
- 静态路径(如
接下来我们将介绍这些。
嵌套路由
在 React Router v6 中,嵌套路由(Nested Routes) 允许你将路由逻辑分层组织,实现复杂的页面布局(如带有共享 Header/Sidebar 的多级页面)。
比如下面这个例子:
<Routes>
<Route path="/" element={<Layout />}>
<Route path="home" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="users" element={<Users />}>
<Route path=":id" element={<UserDetail />} />
</Route>
</Route>
</Routes>
一般情况下<Route>都是单标签,在这里我们可以看到 <Route path="/" element={<Layout />}></Route>成了双标签,在标签里面的<Route>全部成了它的子路由,可以实现共享<Layout />组件,这样无论怎么跳转,页面都会有一个<Layout />组件了。
那么父元素的组件是如何将子元素的组件嵌入到自己身上的?
那就要说到<Outlet />了
<Outlet />
<Outlet /> 的作用:
- 占位符组件:标记子路由内容应该渲染在父路由的哪个位置(类似 Vue 的
<router-view>或 Angular 的<router-outlet>)。 - 动态插入:当 URL 匹配嵌套路由时,React Router 会用子路由的
element替换<Outlet />。
我们就写上面的<Layout />组件吧:
// 父路由组件 (Layout.jsx)
import { Outlet } from 'react-router-dom';
function Layout() {
return (
<div>
<header>Shared Header</header>
<main>
{/* 子路由的内容会渲染在这里 */}
<Outlet />
</main>
<footer>Shared Footer</footer>
</div>
);
}
我们看到了<Outlet />,这就是占位符,<Route path="/" element={<Layout />}></Route>中的各个子组件都会渲染在<Outlet />这里。
在这里你有没有想过,当我们直接访问了父组件的时候,会不会展示子组件呢?
每个子组件都对应着一个地址,但我们访问的是:localhost:5173/,这个时候会有子组件显示吗?
答案是不会,但我们可以加一个属性index,选中一个默认子路由。
Route 的index属性
这个属性用于选定默认子路由,能让父组件渲染的同时,让这一个子组件也跟着渲染。
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
{/*可以替代path,毕竟默认访问就可以展示这个组件,再加个path也没什么必要了,当然你想加一个path也可以*/}
<Route path="about" element={<About />} />
<Route path="users" element={<Users />}>
<Route path=":id" element={<UserDetail />} />
</Route>
</Route>
</Routes>
这个时候当我们访问父路由的时候,子路由默认渲染的就是<Home />组件。
导航与跳转
说了这么多路由了,那我们不能总是在页面上输入路由实现跳转吧?那样真的就会太原始了hhhhhh,所以我们接下来继续看 跳转的方法!(学无止境啊真是~)
声明式导航
<Link to="path"> :用于页面跳转,不会刷新整个页面(SPA 路由切换)。
<NavLink> :是 <Link> 的增强版,可以自动添加 active 类名(用于高亮当前选中项)。
比如我们可以在Layout中这么写:
// 父路由组件 (Layout.jsx)
import { Outlet } from 'react-router-dom';
function Layout() {
return (
<div>
<header>
<nav>
<Link to="/">Home</Link>
<Link to="/users">Users</Link>
<Link to="/:id">userDetail</Link>
<NavLink to="/about" style={({ isActive }) => ({color: isActive ? "red" : "black",})}>
About
</NavLink>
</nav>
</header>
<main>
{/* 子路由的内容会渲染在这里 */}
<Outlet />
</main>
<footer>Shared Footer</footer>
</div>
);
}
编程式导航
useNavigate() :它为 React Router v6 提供的一个 Hook,用于在函数组件中以编程方式控制导航(如提交表单后跳转)
它是这么用的:
import { useNavigate } from "react-router-dom";
function MyComponent() {
const navigate = useNavigate();
const handleClick = () => {
navigate("/target-path"); // 跳转到指定路径
};
return <button onClick={handleClick}>跳转</button>;
}
它和<Link>的设计理念是不同的,<Link>是一个组件,主要是用户自己想要跳转的时候就可以点击<Link>跳转,而navigate经常用于函数的异步操作或者表单的提交中,通常是由代码逻辑触发的,所以两者的适用场景就不同。
注意:navigate()里面的地址中+/,就会从应用的根路径解析,不加/,就会从当前路径解析。
小插曲::id
这是动态路径,这里可以是任何值,比如这个例子:
<Route path=":id" element={<UserDetail />} />
如果我的根路径是localhost:5173,那么我可以访问localhost:5173/Skye,localhost:5173/12333......
这个参数一般都是需要我们在组件中获取拿来用的,我们可以用useParams
useParams
在 React Router v6 中,useParams 是一个 Hook,用于获取当前路由的动态参数(即 URL 中的占位符变量)。它常用于从 URL 中提取值(如 ID、用户名等),以便在组件中动态渲染内容或发起数据请求。
假如我们在上面的<UserDetail />中用来获取一些信息:
import { useEffect } from "react";
function UserDetail() {
const { userId } = useParams();
useEffect(() => {
if (userId) {
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((data) => console.log(data));
}
}, [userId]);
return <div>Loading user data...</div>;
}
懒加载/路由守卫
最后!我们说一点高级的!
懒加载
引入页面组件后,页面组件全部会加载,如果项目过大,比如30个组件同时加载,会影响性能,让你的加载很慢,这个时候我们就可以使用懒加载了。
用法:
import { lazy, Suspense } from 'react'
{/*把import全部改为:👇*/}
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const NotFound = lazy(() => import('./pages/NotFound'))
const Pay = lazy(() => import('./pages/Pay'))
const Login = lazy(() => import('./pages/Login'))
{/*在组件中该怎么用就怎么用*/}
function App() {
return (
<>
<Router>
<Navigation />
<Suspense fallback={<div>loading</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* 需要校验权限的路由 */}
<Route path="/pay" element={
<ProtectRoute>
<Pay />
<div>123</div>
<div>456</div>
</ProtectRoute>
} />
<Route path='*' element={<NotFound />} />
<Route path="/login" element={<Login />} />
</Routes>
</Suspense>
</Router>
</>
)
}
export default App
React.lazy接受一个返回import()的函数。import()是动态导入语法(返回Promise),会在组件首次渲染时加载对应的模块。
它必须要搭载<Suspense>使用,Suspense意为悬停,加载是异步的,在没加载完成前,就会显示 Suspense 的 fallback(备用组件,目标显示失败或加载中的时候会显示),fallback可以接收一个元素,也能接收一个组件。
路由守卫
想一想有的网站是不是登录后才能评论点赞?在你想点赞或者评论的时候,会弹出登录的窗口,没错,路由守卫就是干这个的。
在用户做了一些没权限的操作需要验证是否有权限的时候,就用上它了,比如:没登录就要评论,就用守卫拦截了,把它重定向到登录页面。
用这个例子来看:
<Route path="/pay" element={
<ProtectRoute>
<Pay />
<div>123</div>
<div>456</div>
</ProtectRoute>
} />
要访问/pay路由时,会加载<ProtectRoute>组件,其他组件都是它的子组件,我们要完成一个验证功能,比如我们利用localStorage进行验证,如果里面有特定值,我们就返回子组件,没有就重定向到登录页面:
import {
Navigate,
useLocation
} from 'react-router-dom';
// 鉴权组件
const ProtectedRoute = (props) => {
const { children } = props; // children是ProtectedRoute包裹的所有子组件,react就是这么设计的
const { pathname } = useLocation(); // 获取当前路径
const isLogin = localStorage.getItem('isLogin') === 'true';
if (!isLogin) {
console.log('未登录');
return (
<Navigate to="/login" state={{ from: pathname }} />
// 重定向到登录页
)
}
return children // 返回子组件
}
export default ProtectedRoute
<Navigate>
<Navigate>通过 JSX 组件触发跳转(类似重定向),它会在组件渲染时自动触发导航。
作用和 useNavigate基本一致,都是为了跳转而用,用的时候区分一下需求和场景即可:
-
用
<Navigate>:- 需要在 渲染阶段 直接跳转(如权限拦截、404 处理)。
- 逻辑简单,直接依赖条件渲染。
-
用
useNavigate:- 需要 手动触发跳转(如按钮点击、API 调用后)。
- 需要更复杂的导航逻辑(如跳转前确认、动态路径计算)。
总结
OK,这一期就是这样了,我们从Router一路讲到路由守卫,当你读完这篇文章,我相信你一定对于react router有了很深的理解了,我们只要记住Router的设计都是为了热更新和用户体验而生就够了,Router能实现不刷新的跳转页面,会节省性能。