| 特性方面 | React Router v6 | React Router v5 |
|---|---|---|
| 路由容器 | <Routes> 替代 <Switch> |
<Switch> |
| 路由定义 | element={<Component />} |
component={Component} |
| 嵌套路由 | 更简洁,在父路由中嵌套子路由,父组件使用 <Outlet> |
在每层主组件中配置 Route |
| 编程式导航 | useNavigate Hook |
useHistory Hook 或 this.props.history |
| 路由参数获取 | 使用 useParams 等 Hooks |
通过组件的 props.match.params 获取 |
| 重定向 | <Navigate> 组件 |
<Redirect> 组件 |
| 路由表配置 | 支持 useRoutes 钩子 |
使用 react-router-config 包 (需额外安装) |
| 活动链接样式 | <NavLink> 的 className 接受函数 |
<NavLink> 的 activeClassName 属性 |
| 相对路径解析 | 支持 | 不支持 |
| 包大小 | 更小 (约10.8kB) | 较大 (约20.8kB) |
1. 安装与基本设置
1.1 安装
使用 npm 或 yarn 安装 react-router-dom(针对 Web 项目):
bash
npm install react-router-dom@6
1.2 包裹应用
在你的应用入口文件(如 index.js 或 main.jsx)中,使用 BrowserRouter 或 HashRouter 包裹你的根组件,以确保路由功能在整个应用中可用。
jsx
// index.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom'; // 引入 BrowserRouter
import App from './App';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<BrowserRouter> {/* 使用 BrowserRouter 包裹应用 */}
<App />
</BrowserRouter>
);
BrowserRouter:利用 HTML5 History API(pushState,replaceState,popstate事件)来保持 UI 与 URL 的同步。URL 看起来更整洁(如example.com/path),但需要服务器配置以支持所有路径都返回应用入口页面。HashRouter:使用 URL 的哈希部分(#)进行路由。URL 会包含一个哈希符号(如example.com/#/path),无需特殊的服务器配置,兼容性更好,但看起来不那么美观。
2. 路由定义与渲染
2.1 <Routes> 与 <Route>
在 v6 中,<Switch> 组件被 <Routes> 组件取代 。<Routes> 和 <Route> 必须配合使用,且 <Route> 必须被 <Routes> 包裹。
最大的变化之一是 <Route> 使用 element 属性来指定要渲染的 React 元素 ,取代了 v5 中的 component 和 render 属性。
jsx
// v5 写法
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
// v6 写法
<Routes>
<Route path="/" element={<Home />} /> {/* 使用 element 属性 */}
<Route path="/about" element={<About />} />
<Route path="users/:userId" element={<UserProfile />} /> {/* 动态路由 */}
<Route path="*" element={<NotFound />} /> {/* 404 页面,匹配未定义的路由 */}
</Routes>
<Routes> 组件会智能地选择最佳匹配 的路由进行渲染,不再像 v5 的 <Switch> 那样严格依赖代码顺序。这意味着你无需再频繁使用 exact 属性(实际上在 v6 中移除了该属性)。
2.2 使用 useRoutes 定义路由表
v6 推荐使用 useRoutes Hook 来通过一个 JavaScript 对象(路由表) 定义你的路由结构,这使得路由配置更加集中和可读,特别适合中大型项目。
jsx
// src/routes/index.js
import { useRoutes } from 'react-router-dom';
import Home from '../pages/Home';
import About from '../pages/About';
import UserLayout from '../layouts/UserLayout';
import UserProfile from '../pages/UserProfile';
import UserSettings from '../pages/UserSettings';
import NotFound from '../pages/NotFound';
export default function Router() {
return useRoutes([
{
path: '/',
element: <Home />
},
{
path: '/about',
element: <About />
},
{
path: '/user',
element: <UserLayout />,
children: [ // 嵌套路由通过 children 定义
{ index: true, element: <UserDashboard /> }, // index 属性指定默认子路由
{ path: 'profile', element: <UserProfile /> },
{ path: 'settings', element: <UserSettings /> }
]
},
{
path: '*',
element: <NotFound />
}
]);
}
然后在你的 App.js 中使用这个路由表:
jsx
// App.js
import Router from './routes';
function App() {
return (
<div className="App">
<Router />
</div>
);
}
3. 导航
3.1 <Link> 与 <NavLink>
<Link> 是用于导航的主要组件,它最终会被渲染成 <a> 标签,但不会触发整个页面的刷新。
<NavLink> 是一种特殊的 <Link>,它能够知道自己的 active 状态,非常适合用于导航菜单或标签页。
在 v6 中,<NavLink> 的样式设置方式发生了变化:
jsx
import { Link, NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
<Link to="/">首页</Link> {/* 普通链接 */}
<NavLink
to="about"
className={({ isActive }) => // 通过函数根据 isActive 返回类名
isActive ? 'active-link' : 'normal-link'
}
style={({ isActive }) => ({ // 内联样式也可以
fontWeight: isActive ? 'bold' : 'normal',
})}
>
关于我们
</NavLink>
{/* end 属性可阻止匹配子路由时自身高亮 */}
<NavLink to="home" end >Home</NavLink>
</nav>
);
}
v5 中常用的 activeClassName 和 activeStyle 属性在 v6 中已被移除。
3.2 编程式导航:useNavigate
在 v6 中,useHistory Hook 被 useNavigate Hook 取代了。
jsx
import { useNavigate } from 'react-router-dom';
function MyComponent() {
const navigate = useNavigate(); // 获取 navigate 函数
const handleClick = () => {
// 跳转到指定路径
navigate('/home');
// 替换当前历史记录条目
navigate('/home', { replace: true });
// 导航到上一页(相当于 history.go(-1))
navigate(-1);
// 导航到前两页(相当于 history.go(2))
navigate(2);
// 也可以传递状态对象
navigate('/user', { state: { from: 'homepage' } });
};
return (
<button onClick={handleClick}>跳转到首页</button>
);
}
navigate 函数支持相对路径 。例如,在 /users 路径下调用 navigate('123') 会跳转到 /users/123。
4. 嵌套路由与 <Outlet>
v6 的嵌套路由设计得非常直观。你只需要在父 <Route>(或路由表的 children 数组)中定义子 <Route>,并在父组件中使用 <Outlet> 组件作为子路由的渲染占位符即可。
jsx
// 路由定义 - 使用 JSX 形式
<Routes>
<Route path="user" element={<UserLayout />}>
<Route index element={<UserDashboard />} /> {/* 默认子路由 */}
<Route path="profile" element={<UserProfile />} />
<Route path="settings" element={<UserSettings />} />
</Route>
</Routes>
// 或者使用路由表形式
{
path: 'user',
element: <UserLayout />,
children: [
{ index: true, element: <UserDashboard /> },
{ path: 'profile', element: <UserProfile /> },
{ path: 'settings', element: <UserSettings /> }
]
}
jsx
// UserLayout.jsx - 父组件
import { Outlet, Link } from 'react-router-dom'; // 引入 Outlet
function UserLayout() {
return (
<div>
<h2>用户中心</h2>
<nav>
<Link to="profile">资料</Link> {/* 相对路径链接到 /user/profile */}
<Link to="settings">设置</Link> {/* 相对路径链接到 /user/settings */}
</nav>
{/* 子路由组件将在此处渲染 */}
<Outlet />
</div>
);
}
当访问 /user 时,<UserLayout> 会渲染,其内部的 <Outlet> 则会渲染索引路由 <UserDashboard>。访问 /user/profile 时,<Outlet> 会替换为 <UserProfile>。
5. 数据获取与参数处理
5.1 URL 参数:useParams
动态路径参数 (如 :userId)可以通过 useParams Hook 获取。
jsx
// 路由定义
<Route path="users/:userId" element={<UserDetail />} />
// UserDetail.jsx
import { useParams } from 'react-router-dom';
function UserDetail() {
let { userId } = useParams(); // 获取 URL 参数
// 使用 userId 获取用户数据...
return <div>用户 ID: {userId}</div>;
}
5.2 查询参数:useSearchParams
URL 查询参数 (如 ?name=John&age=30)可以通过 useSearchParams Hook 获取和设置,它返回一个类似于 useState 的数组。
jsx
import { useSearchParams } from 'react-router-dom';
function UserList() {
const [searchParams, setSearchParams] = useSearchParams();
const nameFilter = searchParams.get('name'); // 获取 'name' 参数
const ageFilter = searchParams.get('age'); // 获取 'age' 参数
const updateFilters = () => {
// 设置查询参数
setSearchParams({ name: 'John', age: 30 });
};
return (
<div>
<div>当前筛选: Name - {nameFilter}, Age - {ageFilter}</div>
<button onClick={updateFilters}>设置筛选</button>
</div>
);
}
5.3 位置状态:useLocation 与 state
你可以使用 useLocation Hook 来获取当前 location 信息,包括通过 navigate(to, { state }) 或 <Link to={pathname} state={state} /> 传递的 state 数据。
jsx
import { useLocation } from 'react-router-dom';
function SomeComponent() {
const location = useLocation();
console.log(location.pathname); // 当前路径
console.log(location.state); // 传递的状态对象,例如 { from: 'homepage' }
return <div>当前位置: {location.pathname}</div>;
}
状态 state 在页面刷新时,如果使用的是 BrowserRouter 通常会保留,而 HashRouter 可能会导致状态丢失。
6. 重定向与特殊功能
6.1 重定向:<Navigate>
在 v6 中,<Redirect> 被 <Navigate> 组件取代。
jsx
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ isAuthenticated, children }) {
// 如果未登录,则重定向到登录页
return isAuthenticated ? children : <Navigate to="/login" replace />;
}
// 在路由中使用
<Route
path="/dashboard"
element={
<ProtectedRoute isAuthenticated={user != null}>
<Dashboard />
</ProtectedRoute>
}
/>
replace 属性决定是替换历史记录中的当前条目(true)还是添加一个新条目(false,默认)。
6.2 路由懒加载
你可以使用 React 的 lazy 和 Suspense 与 React Router 结合来实现组件的懒加载,优化应用的初始加载速度。
jsx
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home')); // 懒加载 Home 组件
const About = lazy(() => import('./pages/About')); // 懒加载 About 组件
function App() {
return (
<Suspense fallback={<div>Loading...</div>}> {/* 加载中的回退UI */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
);
}
7. 更多实用技巧与 v5 迁移要点
7.1 路由守卫(受保护路由)
你可以通过组合路由和条件渲染来实现路由守卫,例如验证用户是否已登录。
jsx
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ isAuthenticated, children }) {
return isAuthenticated ? children : <Navigate to="/login" replace state={{ from: location.pathname }} />;
}
// 使用
<Routes>
<Route path="/dashboard" element={
<ProtectedRoute isAuthenticated={user != null}>
<Dashboard />
</ProtectedRoute>
} />
<Route path="/login" element={<Login />} />
</Routes>
7.2 从 v5 迁移到 v6 的主要注意事项
如果你有正在使用 v5 的项目,迁移到 v6 主要涉及以下变化:
- 将
<Switch>替换为<Routes>。 <Route>的component和render属性改为element属性 ,并且传递 JSX 元素(例如element={<MyComponent />})。- 使用
useNavigate代替useHistory进行编程式导航。 - 嵌套路由配置方式变化 ,充分利用
<Route>嵌套和<Outlet>。 - 相对路径链接 :
<Link to="child">和navigate('child')现在默认相对于父路由的路径,而不是绝对路径。 - 移除
<Redirect>,使用<Navigate>替代。 - 移除
exact属性,因为 v6 的匹配算法默认就是匹配完整路径。 - 路由组件不再自动接收
history,location,match作为 props ,必须使用 Hooks(如useNavigate,useLocation,useParams)来获取。 - 移除
withRouter高阶组件,函数组件使用 Hooks,类组件需要自行封装。 <NavLink>的activeClassName和activeStyle属性被移除 ,改用函数形式的className或style属性。
建议仔细阅读官方迁移指南以获取完整信息。
8. 总结
React Router v6 通过更声明式的 API (如 element 属性)、更强大的嵌套路由支持 (通过 <Outlet>)以及与 Hooks 更深入的集成 (如 useNavigate, useParams),极大地改善了开发体验。它的匹配算法更智能,配置也更灵活(支持 JSX 和路由表两种方式)。