React Router
React Router 是 React 生态系统中最流行的路由解决方案,它允许我们在单页面应用(SPA)中创建导航,同时保持应用的快速响应。通过 React Router,开发者可以轻松地实现页面之间的跳转,而无需完全重新加载整个应用。
1. 安装 React Router
首先,你需要安装 react-router-dom
来在 React 项目中使用路由。
npm install react-router-dom
或者使用 yarn:
yarn add react-router-dom
2. 核心概念
在开始之前,先了解 React Router 的几个核心概念(其实和vue差不多):
- Router(路由器): 提供应用中的路由上下文。最常用的
BrowserRouter
通过浏览器的历史记录实现路由。 - Route(路由): 定义了特定 URL 下应渲染的组件。通过 URL 匹配来显示内容,显示的具体区域。
- Link(链接): 类似于 HTML 的
<a>
标签,用于导航到不同的路由。 - Switch: 包装
Route
,确保一次只渲染一个匹配的路由。 - useParams: 用于获取 URL 中的动态参数。
- useNavigate: 用于在编程式导航时控制路由跳转。
3. 基础使用
3.1 使用 BrowserRouter
BrowserRouter
是基于 HTML5 history
API 的路由器,允许创建干净的 URL,如 /about
或 /contact
。
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import Home from './Home';
import About from './About';
function App() {
return (
<Router>
<div>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</div>
</Router>
);
}
export default App;
3.2 创建路由和组件
每个路由定义了在某个路径下应该渲染的组件。在这个例子中,我们有两个组件 Home
和 About
,分别对应根路径 /
和 /about
。
home (vite 创建的模板文件)
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '../../../public/vite.svg'
import '../../App.css'
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
)
}
export default App
about
function About() {
return(
<div>
<h1>About</h1>
<p>react 写多了还是挺爽的</p>
</div>
)
}
export default About;
此时访问对应的路由就可以显示对应的页面
3.3 使用 Link
进行导航
使用 Link
组件代替传统的 <a>
标签来实现客户端路由导航。它可以防止页面重新加载。
import { Link } from 'react-router-dom';
import {useState} from "react";
function Navbar() {
const [route, setRoute] = useState([{
path: '/',
name: '主页'
}, {
path: '/about',
name: '关于'
}]);
return (
<div style={{top: '10%', alignItems: 'center', justifyContent: 'center', position: 'fixed', width: '100%'}}>
<nav style={{backgroundColor: '#fff', display: 'flex'}}>
{route.map((item, index) => (
<Link style={{margin: '0 10px'}} key={index} to={item.path}>
{item.name}
</Link>
))}
</nav>
</div>
);
}
export default Navbar;
将 Navbar
添加到 App
中,使其出现在页面的顶部:
注意通常Router组件是顶级目录 Routes 是route的区域,route是选择区域
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import Home from './Home';
import About from './About';
import Navbar from './navigation.tsx';
function App() {
return (
<Router>
<Navbar />
{/*下面是显示区域*/}
<div>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</div>
</Router>
);
}
export default App;
4. 动态路由和 URL 参数
有时候你需要在 URL 中包含动态部分,例如用户 ID 或文章 ID。React Router 允许你在路径中定义动态参数。
4.1 动态路由示例
import { useParams } from 'react-router-dom';
function UserSlice() {
const { userId } = useParams(); // 获取动态参数
return <h1>用户 ID: {userId}</h1>;
}
export default UserSlice;
在路由中定义动态参数:
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/user/:userId" element={<UserSlice />} /> {/* 动态路由 */}
</Routes>
</Router>
);
}
如果你访问 /user/42
,UserSlice
组件将会显示 用户 ID: 42
。
4.2 使用 useParams
获取动态参数
在上述代码中,useParams
钩子用来获取 URL 中的动态部分。你可以通过 useParams
获取匹配的 URL 参数,并在组件中使用。
5. 编程式导航
在有些情况下,你可能需要在事件(如按钮点击)中进行导航。React Router 提供了 useNavigate
钩子来实现这种需求。
5.1 使用 useNavigate
进行导航
import { useNavigate } from 'react-router-dom';
function Home() {
const navigate = useNavigate();
const goToAbout = () => {
navigate('/about'); // 导航到 /about
};
return (
<div>
<h1>这是主页</h1>
<button onClick={goToAbout}>去关于页面</button>
</div>
);
}
export default Home;
当你点击按钮时,页面会导航到 /about
路由。
6. 路由嵌套
React Router 支持嵌套路由,即在一个路由中嵌套另一个路由,方便构建复杂的页面结构。
在单页面应用中,管理后台特别用到这个比较多, 主要用于在主布局(/)下动态切换内容区域,而 Route 可以用于独立页面,比如 Login 或 404 页面。
6.1 嵌套路由示例
有点vue的子路由
function Dashboard() {
return (
<div>
<h1>仪表盘</h1>
<div style={{marginTop: '20px',display: 'flex', flexDirection: 'column',border: '1px solid #ccc',padding: '20px'}}>
<Outlet /> {/* 渲染子路由区域 */}
</div>
</div>
);
}
function Settings() {
useEffect(() => {
console.log('Settings');
}, []);
return (
<div>
<h2>设置</h2>
</div>);
}
function Status() {
useEffect(() => {
console.log('状态');
}, []);
return (
<div>
<h2>状态</h2>
</div>);
}
function App() {
return (
<Router>
<Navbar />
{/*下面是显示区域*/}
<div>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/device/:Device" element={<Device/>} />
{/*演示子路由*/}
<Route path="/dashboard" element={<Dashboard />}>
<Route path="settings" element={<Settings />} /> {/* 子路由 */}
<Route path="status" element={<Status />} /> {/* 子路由 */}
</Route>
</Routes>
</div>
</Router>
);
}
export default App;
在这个示例中,/dashboard/settings
和 /dashboard/profile
是 Dashboard
的子路由,Outlet
用于在 Dashboard
组件中渲染当前匹配的子路由。
7. 重定向和 404 页面
7.1 重定向
有时你需要将用户从一个路由自动导航到另一个路由。React Router 提供了 Navigate
组件来实现重定向。
import { Navigate } from 'react-router-dom';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/old-about" element={<Navigate to="/about" />} /> {/* 重定向 */}
</Routes>
</Router>
);
}
访问 /old-about
会自动重定向到 /about
。
7.2 404 页面(未匹配路由)
你可以使用一个匹配不到任何路径的路由来显示 404 页面。
404
import { Link } from 'react-router-dom';
import './NotFound.css'; // 可以创建一个独立的CSS文件管理样式
const NotFound = () => {
return (
<div className="not-found-container">
<div className="not-found-content">
<h1 className="not-found-title">404</h1>
<p className="not-found-text">Oops! The page you're looking for doesn't exist.</p>
<Link to="/" className="back-home-btn">Go Back Home</Link>
</div>
</div>
);
};
export default NotFound;
css
/* NotFound.css */
/* 外层容器,用于垂直和水平居中对齐内容 */
.not-found-container {
display: flex; /* 使用 flexbox 布局 */
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100vh; /* 占据整个视口高度 */
background-color: #f7f7f7; /* 背景颜色为浅灰色 */
text-align: center; /* 文本居中对齐 */
}
/* 404 页面内容的容器,包含错误信息和按钮 */
.not-found-content {
background: white; /* 背景颜色为白色 */
padding: 50px; /* 四周内边距 */
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); /* 添加阴影效果,增强立体感 */
border-radius: 10px; /* 圆角边框 */
animation: fadeIn 1.2s ease-in-out; /* 使用 fadeIn 动画效果进入 */
}
/* 404 标题样式 */
.not-found-title {
font-size: 6rem; /* 标题字体大小,较大以突出 */
font-weight: bold; /* 粗体字体 */
color: #ff6b6b; /* 字体颜色为红色(更显眼) */
margin-bottom: 20px; /* 标题和下面内容之间的距离 */
}
/* 错误信息文本 */
.not-found-text {
font-size: 1.5rem; /* 字体大小适中,便于阅读 */
color: #333; /* 字体颜色为深灰色,提升可读性 */
margin-bottom: 30px; /* 文本和按钮之间的距离 */
}
/* 返回首页按钮样式 */
.back-home-btn {
padding: 10px 20px; /* 按钮内边距 */
font-size: 1.2rem; /* 字体大小 */
color: white; /* 按钮文字颜色为白色 */
background-color: #4caf50; /* 按钮背景颜色为绿色 */
border: none; /* 无边框 */
border-radius: 5px; /* 圆角按钮 */
text-decoration: none; /* 去掉文本链接的下划线 */
transition: background-color 0.3s ease; /* 按钮背景色的渐变过渡效果 */
}
/* 鼠标悬停在按钮上的效果 */
.back-home-btn:hover {
background-color: #45a049; /* 悬停时按钮的背景颜色变为深绿色 */
}
/* fadeIn 动画效果 */
@keyframes fadeIn {
from {
opacity: 0; /* 从完全透明开始 */
transform: translateY(20px); /* 初始位置为向下偏移 20px */
}
to {
opacity: 1; /* 最终完全可见 */
transform: translateY(0); /* 最终位置为原位 */
}
}
function NotFound() {
return <h1>404 - 页面未找到</h1>;
}
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} /> {/* 匹配所有未定义的路径 */}
</Routes>
</Router>
);
}
8. 路由守卫(保护路由)
保护路由用于限制未授权用户访问某些页面。通常,结合 useNavigate
钩子和用户登录状态来实现。
8.1 创建受保护的路由组件
在应用中使用:
const App = () => {
return (
<Router>
<Routes>
{/* 需要登录才能访问的页面 */}
<Route
path="/"
element={
<AuthGuard>
<Logout Header={Header} Sider={Sider} Content={Content} Footer={Footer} />
</AuthGuard>
}
>
子路由 渲染在父路由的outlet中
<Route index element={<Index />} /> {/* 首页 */}
<Route path="app" element={<Dashboard />} /> {/* App 页面 */}
<Route path="order" element={<Order />} /> {/* 订单页面 */}
</Route>
{/* 登录页面,已登录用户不能访问 */}
<Route
path="/login"
element={
<LoginGuard>
<Login />
</LoginGuard>
}
/>
{/* 404 页面 */}
<Route path="*" element={<NotFound />} />
</Routes>
</Router>
);
};
//路由守卫代码
/**
* 路由守卫和插槽一样 也是通过props方式实现的
* @param children
* @constructor
*/
// AuthGuard: 用于保护需要登录才能访问的页面
const AuthGuard = ({ children }) => {
const isAuthenticated = Boolean(localStorage.getItem('token'));
const location = useLocation();
if (!isAuthenticated) {
// 如果用户未登录,重定向到登录页面
return <Navigate to="/login" replace state={{ from: location }} />;
}
return children;
};
// LoginGuard: 用于防止已登录用户访问登录页面
const LoginGuard = ({ children }) => {
const isAuthenticated = Boolean(localStorage.getItem('token'));
if (isAuthenticated) {
// 如果用户已登录,重定向到主页
return <Navigate to="/" replace />;
}
return children;
};
export default App;
如果用户没有登录,将会被重定向到主页。
本质路由守卫也就是在路由组件的插槽(渲染子组件)的时候在包一层组件,也是通过插槽实现props,然后决定规则跳转
9. 路由过渡动画
通过 React Router 与动画库(如 react-transition-group
)结合,可以实现路由过渡动画效果。
9.1 使用 react-transition-group
动画库
安装库:
npm install react-transition-group
示例代码:router的作用是是为路由提供上下文,并且动画标签包裹的只能是路由,所以这里要把之前演示的navi 和路由进行分开
function AppContent() {
const location = useLocation();
return (
<TransitionGroup>
<CSSTransition key={location.pathname} timeout={300} classNames="fade">
<Routes location={location}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/device/:Device" element={
// 2.路由中包裹被渲染的组件
<PrivateRoute>
<Device />
</PrivateRoute>
} />
<Route path="/old-about" element={<Navigate to="/about" />} /> {/* 重定向 */}
{/*演示子路由*/}
<Route path="/dashboard" element={<Dashboard />}>
<Route path="settings" element={<Settings />} /> {/* 子路由 */}
<Route path="status" element={<Status />} /> {/* 子路由 */}
</Route>
<Route path="*" element={<NotFound />} /> {/* 匹配所有未定义的路径 */}
</Routes>
</CSSTransition>
</TransitionGroup>
);
}
function App() {
return (
<Router>
<Navbar />
<AppContent />
</Router>
);
}
你需要为 classNames
提供 CSS 动画:
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 300ms;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 300ms;
}
这样就可以在路由切换时实现淡入淡出的动画效果。