React Router v6: 完整介绍与使用指南
在现代单页应用 (SPA) 中,路由是不可或缺的一部分。它允许用户在应用的不同视图之间导航,而无需重新加载整个页面,从而提供流畅的用户体验。对于 React 应用来说,React Router 是事实上的标准路由解决方案。
自 React Router v6 发布以来,它引入了许多激动人心的新特性、更简洁的 API 和性能优化,使得路由的设置和使用变得更加直观和高效。本文将深入探讨 React Router v6 的核心概念、API 以及如何在你的 React 项目中有效地使用它。
什么是 React Router?
React Router 是一个功能强大且灵活的库,它允许你将 URL 与 React 组件同步。它提供了一套声明式组件,让你可以在应用中轻松地定义路由、导航链接以及处理路由参数等。
为什么选择 React Router v6?
相较于之前的版本,React Router v6 进行了大幅度的改进:
- 更小的包体积:移除了不常用的 API,减少了最终打包文件的大小。
- 更简洁的 API:例如,
Switch被Routes取代,useHistory被useNavigate取代,并且路由的定义方式更具表现力。 - 嵌套路由的改进:嵌套路由的处理更加自然和直观。
- 相对路径:支持在
Route和Link中使用相对路径,提高了路由的灵活性。 - 新的 Hooks:引入了一系列新的 Hooks(如
useRoutes,useMatch),进一步简化了路由逻辑。
安装
首先,你需要在你的 React 项目中安装 React Router v6。
“`bash
npm install react-router-dom
或者
yarn add react-router-dom
“`
react-router-dom 是用于 Web 应用程序的包,它包含了核心的 react-router 功能以及针对 DOM 环境的特定组件(如 BrowserRouter)。
核心概念与组件
React Router v6 的核心思想是组件化路由。一切皆组件,包括你的路由配置。
1. BrowserRouter
BrowserRouter 是最常用的路由容器。它使用 HTML5 历史 API (pushState, replaceState, popstate) 来保持 UI 与 URL 的同步。通常,你会在应用的根组件中包裹你的整个应用。
“`jsx
// src/index.js 或 src/App.js
import React from ‘react’;
import ReactDOM from ‘react-dom/client’;
import { BrowserRouter } from ‘react-router-dom’;
import App from ‘./App’;
const root = ReactDOM.createRoot(document.getElementById(‘root’));
root.render(
);
“`
2. Routes
Routes 组件取代了 v5 中的 Switch。它的作用是遍历其所有的 Route 子元素,并渲染第一个匹配当前 URL 的 Route。
3. Route
Route 组件用于定义单个路由。它接受两个主要的 props:
path: 匹配的 URL 路径。element: 当path匹配时要渲染的 React 元素。
“`jsx
import { Routes, Route } from ‘react-router-dom’;
import Home from ‘./pages/Home’;
import About from ‘./pages/About’;
import Contact from ‘./pages/Contact’;
function App() {
return (
);
}
“`
4. Link
Link 组件是用于在应用中创建导航链接的首选方式。它会渲染一个 <a> 标签,但会阻止浏览器默认的页面刷新行为,而是由 React Router 接管导航。
“`jsx
import { Link } from ‘react-router-dom’;
function Navigation() {
return (
);
}
“`
5. NavLink
NavLink 是一个特殊版本的 Link,它允许你在当前路由匹配时,为链接添加 active 类或应用自定义样式,常用于导航菜单高亮当前页。
“`jsx
import { NavLink } from ‘react-router-dom’;
function Navigation() {
return (
);
}
“`
NavLink 的 className 和 style props 可以接受一个函数,该函数会接收一个包含 isActive 属性的对象。
6. useNavigate Hook
useNavigate Hook 允许你在函数组件中进行编程式导航(即通过代码而不是点击 Link 来改变路由)。它返回一个函数,调用这个函数并传入目标路径即可实现导航。
“`jsx
import { useNavigate } from ‘react-router-dom’;
function Dashboard() {
const navigate = useNavigate();
const handleGoToSettings = () => {
navigate(‘/settings’);
};
const handleGoBack = () => {
navigate(-1); // 相当于浏览器后退
};
return (
Dashboard
);
}
“`
你也可以传递一个对象作为第二个参数,来传递 state:
jsx
navigate('/user/123', { state: { from: 'dashboard' } });
7. useParams Hook
useParams Hook 用于访问当前 URL 中的动态路由参数。
“`jsx
// 定义路由
// 在 UserProfile 组件中
import { useParams } from ‘react-router-dom’;
function UserProfile() {
const { id } = useParams(); // 获取 URL 中的 :id 参数
return (
User Profile
User ID: {id}
);
}
“`
8. useLocation Hook
useLocation Hook 返回一个 location 对象,其中包含当前 URL 的信息,例如路径名 (pathname)、查询字符串 (search) 和 state (state)。
“`jsx
import { useLocation } from ‘react-router-dom’;
function CurrentPageInfo() {
const location = useLocation();
// URL: /products?category=electronics&page=1
// location.pathname: “/products”
// location.search: “?category=electronics&page=1”
// location.state: (如果通过 navigate 传递了 state)
return (
Current Path: {location.pathname}
Query String: {location.search}
{location.state &&
State: {JSON.stringify(location.state)}
}
);
}
“`
要解析查询字符串,你可以使用原生的 URLSearchParams API。
jsx
const queryParams = new URLSearchParams(location.search);
const category = queryParams.get('category'); // "electronics"
9. useMatch Hook
useMatch Hook 允许你检查一个路径是否与当前 URL 匹配,并返回匹配对象(如果匹配)或 null(如果不匹配)。这在某些需要手动判断路由匹配情况的场景中很有用。
“`jsx
import { useMatch } from ‘react-router-dom’;
function CustomNavLink({ to, children }) {
const match = useMatch(to);
const isActive = match !== null;
return (
{children}
);
}
“`
基本使用示例
让我们创建一个简单的应用来演示上述概念:
“`jsx
// src/pages/Home.js
import React from ‘react’;
function Home() {
return
Home Page
;
}
export default Home;
// src/pages/About.js
import React from ‘react’;
function About() {
return
About Us Page
;
}
export default About;
// src/pages/Contact.js
import React from ‘react’;
function Contact() {
return
Contact Us Page
;
}
export default Contact;
// src/App.js
import React from ‘react’;
import { Routes, Route, Link, NavLink } from ‘react-router-dom’;
import Home from ‘./pages/Home’;
import About from ‘./pages/About’;
import Contact from ‘./pages/Contact’;
import UserProfile from ‘./pages/UserProfile’; // 稍后创建
function App() {
return (
<hr />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/users/:id" element={<UserProfile />} />
</Routes>
</div>
);
}
export default App;
// src/index.js (如上所示,包裹 App 组件)
“`
嵌套路由
React Router v6 对嵌套路由的处理更加优雅。你可以在父 Route 的 element 中渲染一个 Outlet 组件,并在子 Route 中定义具体的子路由。
“`jsx
// src/pages/DashboardLayout.js
import React from ‘react’;
import { Outlet, NavLink } from ‘react-router-dom’;
function DashboardLayout() {
return (
Dashboard
{/ 子路由内容将在这里渲染 /}
);
}
export default DashboardLayout;
// src/pages/DashboardOverview.js
import React from ‘react’;
function DashboardOverview() {
return
Dashboard Overview
;
}
export default DashboardOverview;
// src/pages/DashboardSettings.js
import React from ‘react’;
function DashboardSettings() {
return
Dashboard Settings
;
}
export default DashboardSettings;
// src/pages/DashboardReports.js
import React from ‘react’;
function DashboardReports() {
return
Dashboard Reports
;
}
export default DashboardReports;
// src/App.js (更新路由配置)
import React from ‘react’;
import { Routes, Route, Link, NavLink } from ‘react-router-dom’;
import Home from ‘./pages/Home’;
import About from ‘./pages/About’;
import Contact from ‘./pages/Contact’;
import UserProfile from ‘./pages/UserProfile’;
import DashboardLayout from ‘./pages/DashboardLayout’;
import DashboardOverview from ‘./pages/DashboardOverview’;
import DashboardSettings from ‘./pages/DashboardSettings’;
import DashboardReports from ‘./pages/DashboardReports’;
function App() {
return (
<hr />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/users/:id" element={<UserProfile />} />
{/* 嵌套路由 */}
<Route path="/dashboard" element={<DashboardLayout />}>
{/* 默认子路由,当访问 /dashboard 时渲染 */}
<Route index element={<DashboardOverview />} />
<Route path="overview" element={<DashboardOverview />} />
<Route path="settings" element={<DashboardSettings />} />
<Route path="reports" element={<DashboardReports />} />
</Route>
</Routes>
</div>
);
}
export default App;
“`
关键点:
- 父
Route的path不需要以/*结尾。 - 子
Route的path是相对于父路由的,例如settings会匹配/dashboard/settings。 indexprop 用于定义父路由的默认子路由。当访问/dashboard时,DashboardOverview会被渲染。Outlet组件是渲染子路由内容的占位符。
未找到 (404) 路由
处理用户访问不存在的 URL 时的情况。你可以在 Routes 的末尾添加一个 path="*" 的 Route 来捕获所有不匹配的路径。
“`jsx
// src/pages/NotFound.js
import React from ‘react’;
import { Link } from ‘react-router-dom’;
function NotFound() {
return (
404 – Page Not Found
The page you are looking for does not exist.
Go to Home
);
}
export default NotFound;
// src/App.js (更新路由配置)
// …
{/ … 其他路由 … /}
// …
“`
重要提示: 404 路由应该放在所有其他路由定义的最后,因为 Routes 组件会渲染第一个匹配的路由。
认证/受保护路由
在许多应用中,某些页面只允许登录用户访问。你可以创建一个高阶组件 (HOC) 或一个包装组件来处理认证逻辑。
“`jsx
// src/components/RequireAuth.js
import React from ‘react’;
import { useLocation, Navigate } from ‘react-router-dom’;
function RequireAuth({ children }) {
const isAuthenticated = / 你的认证逻辑,例如从 Context 或 Redux 获取 /;
const location = useLocation();
if (!isAuthenticated) {
// 如果未认证,重定向到登录页,并保存当前路径以便登录后返回
return
}
return children;
}
export default RequireAuth;
// src/pages/Login.js
import React, { useState } from ‘react’;
import { useNavigate, useLocation } from ‘react-router-dom’;
function Login() {
const [username, setUsername] = useState(”);
const [password, setPassword] = useState(”);
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || ‘/dashboard’; // 登录成功后要跳转的页面
const handleLogin = (e) => {
e.preventDefault();
// 假设认证成功
console.log(‘Logging in with:’, username, password);
const isAuthenticated = true; // 实际应为认证服务返回的结果
if (isAuthenticated) {
// 模拟设置认证状态
// 这里你需要将 isAuthenticated 状态提升到 App 或 Context 中
// 例如:authContext.login();
navigate(from, { replace: true }); // 登录成功后跳转
} else {
alert('Login failed!');
}
};
return (
Login
);
}
export default Login;
// src/App.js (更新路由配置)
// …
import RequireAuth from ‘./components/RequireAuth’;
import Login from ‘./pages/Login’;
function App() {
// 假设 isAuthenticated 状态在这个地方 (或者通过 Context 提供)
const [isAuthenticated, setIsAuthenticated] = React.useState(false); // 模拟认证状态
// 提供认证上下文
const authContextValue = React.useMemo(() => ({
isAuthenticated,
login: () => setIsAuthenticated(true),
logout: () => setIsAuthenticated(false),
}), [isAuthenticated]);
return (
<hr />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/users/:id" element={<UserProfile />} />
<Route path="/login" element={<Login />} />
{/* 受保护的路由 */}
<Route path="/dashboard" element={<RequireAuth><DashboardLayout /></RequireAuth>}>
<Route index element={<DashboardOverview />} />
<Route path="overview" element={<DashboardOverview />} />
<Route path="settings" element={<DashboardSettings />} />
<Route path="reports" element={<DashboardReports />} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
</div>
</AuthContext.Provider>
);
}
// 模拟 AuthContext
export const AuthContext = React.createContext(null);
export default App;
``RequireAuth
在这个例子中,组件检查用户是否认证。如果未认证,它会使用Navigate组件重定向到/login路径,并使用state` prop 保存用户最初尝试访问的路径,以便登录成功后可以返回。
总结
React Router v6 通过其更简洁的 API、改进的嵌套路由支持和新的 Hooks,极大地简化了 React 应用中的路由管理。掌握 BrowserRouter、Routes、Route、Link、NavLink 以及 useNavigate、useParams、useLocation 等核心概念和 Hooks,你将能够构建出高效、用户友好的单页应用。
始终记得将 BrowserRouter 包裹在应用的根部,将 Routes 组件用于定义路由集合,并根据需要利用其他组件和 Hooks 来实现复杂的导航和页面逻辑。通过实践和不断探索,你会发现 React Router v6 是构建健壮 React 应用的强大工具。