特性方面 | 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 和路由表两种方式)。