react-router 是一个流行的用于 React 应用程序路由的库。它使我们能够轻松定义应用程序的路由,并将它们映射到特定的组件,这样可以很容易地创建复杂的单页面应用,并管理应用程序的不同视图。
react-router 是基于 React 构建的,因此与其他 React 库和工具集成得很好。它在许多 React 应用程序中广泛使用,并被认为是 React 中最佳实践的路由。
一、react-router-dom安装和简介
1. react-router-dom安装
使用 npm 或 yarn 安装 react-router-dom。
npm install --save react-router-dom
yarn add react-router-dom
2. react-router-dom 简介
react-router-dom 是 react-router 的一种实现方案,主要应用于网页端应用,它提供了一些常用的组件进行路由管理。
(1) Router 类组件
- BrowserRouter:history 模式下的路由。
- HashRouter:hash 模式下的路由。
(2) Route 组件
Route 是一个路由配置组件。
(3) Link 组件
Link 类似于 a 标签,可以用于路由跳转。
(4) useNavigate Hook
useNavigate 是一个钩子函数,可以用于路由跳转。
二、react-router-dom 基本使用
1. 引入 react-router-dom
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom';
这里的 BrowserRouter as Router 相当于给 BrowserRouter 起了一个别名叫 Router。Routes 组件用于包裹 Route 组件,Route 组件的父组件必须是 Routes。
2. 简单使用
使用 BrowserRouter 组件包裹 Routes 组件,再用 Routes 组件包裹 Route 组件,在 Route 组件中定义路由信息,就实现了一个简单的 react-router 场景。
Route 组件有 path 和 element 两个属性,path 属性代表路由路径,element 属性代表要渲染的组件。
index.js
import ReactDOM from 'react-dom/client';
import App from './App';
import {BrowserRouter as Router} from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Router>
<App />
</Router>
);
app.js
import './App.css';
import { Routes, Route} from "react-router-dom";
import Home from "./views/Home";
import About from "./views/About";
import Error from "./views/Error";
function App() {
return (
<div className="App">
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/about" element={<About/>}/>
<Route path="*" element={<Error/>}/>
</Routes>
</div>
);
}
export default App;
在上面的案例中,我们定义了一个根路由,指向 Home 页面。还定义一个指定路径路由 /about,指向 About 页面,表示当我们在浏览器地址栏中的页面地址后加上 /about 时,会跳转到 About 页面。
又定义了一个通用路由,指向 Error 页面,path="*" 表示如果浏览器地址栏中的页面地址不是以上定义的2种路由(即其他路由地址)时,就会跳转到 Error 页面。
注意:BrowserRouter 组件最好放在最顶层所有组件之外,这样能确保内部组件使用 Link 做路由跳转时不出错。
3. 路由跳转
跳转路由时,如果路由路径是 / 开头的是绝对路由,否则是相对路由,即相对于当前 URL进行改变。
(1) Link 组件
Link 组件只能在 Router 组件的内部使用,to 属性代表要跳转的路由地址。
import { Link } from "react-router-dom";
<Link to="/about">To About</Link>
(2) NavLink 组件
NavLink 组件和 Link 组件的功能是一致的,区别在于 NavLink 组件可以判断其 to 属性是否是当前匹配到的路由。
NavLink组件的 style 属性或 className 属性可以接收一个函数,函数接收一个含有 isActive 字段的对象为参数,可根据该参数调整样式。
import { Link } from "react-router-dom";
<NavLink to="/about" style={({ isActive }) => ( {color: isActive ? "red" : "black"} )}>To About</NavLink>
(3) useNavigate Hook
使用 useNavigate 钩子函数,可以在 js 代码中实现路由跳转。
useNavigate 必须在 Route 的上下文中使用,即必须包裹在 Router 标签内。
import { useNavigate } from "react-router-dom";
<button onClick={() => navigate("/about")}>To About</button>
4. 路由参数
(1) 路径参数(动态路由匹配)
在 Routes 组件内定义路由。对于动态路由,使用冒号(:)来指定动态部分。例如,要定义一个动态用户路由,可以:
App.js
<Routes>
<Route path="/user/:userId" element={<User />} />
</Routes>
这里的 :userId 是动态部分,可以匹配任何放在 /user/ 后面的路径,比如 /user/123。
在路由的目标组件中,可以使用 useParams 钩子来访问动态参数。例如,在 User 组件中,可以这样获取 userId:
User.js
mport { useParams } from 'react-router-dom';
function User {
let { userId } = useParams();
console.log(userId); // 123
// 使用 userId 作为你的逻辑的一部分
}
export default User;
上述案例中,访问 /user/123 能正确获取到 userId 为123。
(2) 查询参数
查询参数不需要在路由中定义,可以使用 useSearchParams 钩子函数来处理路径中的查询参数。
查询参数位于 URL 中,用于提供额外信息以便服务器能够更具体地处理请求。它们通常出现在网页地址(URL)的末尾,并且遵循一定的格式。
在 URL 中,查询参数是通过问号(?)与网址的其它部分分隔开的,并且可以包含一个或多个键值对。每个键值对都由一个键(key)和一个值(value)组成,用等号(=)连接。如果有多个查询参数,它们之间通常用符号与(&)分隔。
例如,在 URL https://example.com/page?param1=value1¶m2=value2 中, param1=value1 和 param2=value2 是查询参数,它们提供了额外的信息给服务器。
useSearchParams Hook 返回当前组件获取到的查询参数对象及修改这个对象的方法,使用方法和 useState Hook 类似。
在使用 setSearchParams 方法时,必须传入所有查询参数,否则会覆盖已有查询参数。
import { useSearchParams } from 'react-router-dom';
function User() {
// 使用 useSearchParams 钩子
const [searchParams, setSearchParams] = useSearchParams();
// 获取查询参数
const userId = searchParams.get('userId');
// 设置查询参数
const setUserId = (newUserId) => {
setSearchParams({ userId: newUserId });
};
// 渲染组件
return (
<div>
<p>当前的 userId 是:{userId}</p>
<button onClick={() => setUserId('123')}>设置 userId 为 123</button>
</div>
);
}
export default User;
上述案例中,访问 /user?userId=123456 时,效果如下:
点击按钮后,userId 变为 123,符合预期。
(3) 状态参数
react-router 对 window.location 进行了包装,状态参数就位于包装后的 location 对象的 state 属性中。下面为包装后的 location 对象的属性:
pathname | 主机名之后的 URL 地址 |
search | 查询参数 |
hash | 哈希值,用于确定页面滚动的具体位置 |
state | 对于 window.history.state 的包装 |
key | 每个 location 对象拥有一个唯一的 key |
state 不显示在页面上,不会引起页面刷新,可用于记录用户的跳转详情或在跳转时携带信息,可以用在 Link 组件或 navigate 方法中使用状态参数。
{/* Link 中使用状态参数 */}
<Link to="/about" state={{ id: 123 }} >To About</Link>
{/* useNavigate 中使用状态参数 */}
<button onClick={() => navigate("/about", { state: { id: 456 } })}>To About</button>
在目标组件中,使用 useLocation 来获取封装后的 loaction 对象,进而获取状态参数。
const location = useLocation();
console.log(location.state);
5. 路由重定向
当在某个路径 /a ,要重定向到路径 /b 时,可以通过 Navigate 组件进行重定向到其他路径。
import { Navigate } from “react-router-dom”;
function A() {
return <Navigate to=”/b" />;
}
export default A;
三、react-router-dom 进阶使用
1. 嵌套路由
嵌套路由是前端开发中用于组织和管理路由的一种技术,特别适用于构建具有层次结构的页面和应用程序。
在 Route 组件内定义 Route 组件实现嵌套路由,在父组件中使用 Outlet 组件作为子组件的占位符(显示匹配到的子组件)。
在嵌套路由中,如果 URL 仅匹配了父级 URL,则 Outlet 组件中会显示带有 index 属性的子路由,这个子路由称为默认路由。
例3-1:根据用户 id 显示用户详细信息
App.js
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import Home from './views/Home';
import Users from './views/Users';
import UserList from "./views/UserList";
import UserDetail from "./views/UserDetail";
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link> |
<Link to="/users">User</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="users" element={<Users />}>
<Route index element={<UserList />} />
<Route path=":userId" element={<UserDetail />} />
</Route>
</Routes>
</Router>
);
}
export default App;
Users.js
import { Link, Outlet } from "react-router-dom";
function Users() {
return (
<div>
<h2>Users</h2>
<nav>
<Link to="list">User List</Link>
</nav>
<Outlet />
</div>
);
}
export default Users;
UserList.js
import { Link } from "react-router-dom";
function UserList() {
const users = [
{id: "1", name: 'John'},
{id: "2", name: 'Jane'},
{id: "3", name: 'Mary'},
];
return (
<div>
<h2>User List</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
<Link to={`/users/${user.id}`}>{user.name}</Link>
</li>
))}
</ul>
</div>
);
}
export default UserList;
UserDetail.js
import { useParams } from 'react-router-dom';
function UserDetail() {
const { userId } = useParams();
const users = [
{id: "1", name: 'John'},
{id: "2", name: 'Jane'},
{id: "3", name: 'Mary'},
];
return (
<div>
<h3>User Detail</h3>
<h3>id:{userId}</h3>
<h3>name:{users.find((user) => user.id === userId).name}</h3>
</div>
)
}
export default UserDetail;
上述案例中,在根组件 App 中定义了一组嵌套路由,一级路由为 / 和 users。又在 users 中嵌套了默认路由和动态路由 :userid。
在这个嵌套路由中,Users 组件为父组件,UserList 和 UserDetail 组件为子组件。在 Users 组件中使用了 Outlet 组件,作为匹配到的子组件的占位符。可以使用在路由的任何层级。当访问 /users 时,这里触发默认路由显示 UserList 组件。当访问 /users/2 时,会显示 UserDetail 组件。
访问 /users:
访问 /users/2:
2. 多组路由
多组路由指的是在单个应用中设置多个独立的路由组,可以在不同部分的应用中实现独立的路由逻辑,增加了路由结构的灵活性和模块化。
在 react-router-dom 中,可以通过在不同的组件中定义多个 Routes 组件来实现多组路由。
例3-2 管理系统案例
App.js
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import MainSite from './views/MainSite';
import AdminPanel from './views/AdminPanel';
function App() {
return (
<Router>
<Routes>
<Route path="*" element={<MainSite />} />
<Route path="admin/*" element={<AdminPanel />} />
</Routes>
</Router>
);
}
export default App;
MainSite.js
import { Routes, Route, Link } from 'react-router-dom';
import Home from './Home';
import About from './About';
function MainSite() {
return (
<div>
<nav>
<Link to="/">Home</Link> |
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
</Routes>
</div>
);
}
export default MainSite;
AdminPanel.js
import { Routes, Route, Link } from 'react-router-dom';
import Dashboard from './Dashboard';
import Users from './Users';
function AdminPanel() {
return (
<div>
<nav>
<Link to="/admin/dashboard">Dashboard</Link> |
<Link to="/admin/users">Users</Link>
</nav>
<Routes>
<Route path="dashboard" element={<Dashboard />} />
<Route path="users" element={<Users />} />
</Routes>
</div>
);
}
export default AdminPanel;
上述案例构建了一个具有两个独立路由组的应用,这两个路由组分别是主站(MainSite 组件)和管理面板(AdminPanel 组件)。MainSite 组件包含了应用的主要页面,如首页(Home)和关于页面(About);AdminPanel 组件是应用的管理面板,包含了仪表盘(Dashboard)和用户管理(Users) 界面。
App.js 中定义了应用的顶级路由结构,使用 Routes 来定义两个主要路由路径:* 和 /admin/*。每个路径指向对应的 MainSite 和 AdminPanel 组件。* 路径指向 MainSite 组件,该组件处理与主站相关的子路由。/admin/* 路径指向 AdminPanel 组件,该组件处理与管理面板相关的子路由。
当访问 / 时,展示 MainSite 组件和 Home 组件;访问 /about,展示 MainSite 组件和 About 组件;访问 /admin,展示 AdminPanel 组件;访问 /admin/dashboard,展示 AdminPanel 组件和 Dashboard 组件。
3. 布局路由
布局路由是一种组织和管理路由的方式,它允许开发者将路由与其对应的布局分开管理。这在复杂的应用中特别有用,因为它可以帮助保持代码的清晰和模块化。
布局路由是指一种模式,其中一组路由共享相同的布局组件。这意味着这些路由的所有页面都将有相同的外观和感觉,例如共享的头部和底部导航栏。布局组件负责渲染这些共享元素,而特定的子路由则负责渲染页面的主要内容。
例3-3 升级版管理系统案例
App.js
同案例3-2。
MainSite.js
import React from 'react';
import {Routes, Route, Link} from 'react-router-dom';
import Home from './Home';
import About from './About';
import Header from './Header';
import Footer from './Footer';
import Error from "./Error";
function MainSite() {
return (
<div>
<Header />
<nav>
<Link to="/">Home</Link> |
<Link to="/about">About</Link>
</nav>
<Routes>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
{/* 更多主站的路由可以在这里添加 */}
{/* 捕获所有未匹配的路由并显示 Error 组件 */}
<Route path="*" element={<Error />} />
</Routes>
<Footer />
</div>
);
}
export default MainSite;
AdminPanel.js
import React from 'react';
import {Routes, Route, Link} from 'react-router-dom';
import Dashboard from './Dashboard';
import Users from "./Users";
import AdminHeader from './AdminHeader';
import AdminFooter from './AdminFooter';
import Error from "./Error";
function AdminPanel() {
return (
<div>
<AdminHeader />
<nav>
<Link to="/admin">Dashboard</Link> |
<Link to="/admin/users">Users</Link>
</nav>
<Routes>
<Route path="" element={<Dashboard />} />
<Route path="users" element={<Users />} />
{/* 更多管理面板的路由可以在这里添加 */}
{/* 捕获所有未匹配的路由并显示 Error 组件 */}
<Route path="*" element={<Error />} />
</Routes>
<AdminFooter />
</div>
);
}
export default AdminPanel;
上述案例中,我们在 MainSite 和 AdminPanel 中加入了 Header 和 Footer 布局组件,同时添加了一个用于处理所有未匹配路由的 Error 组件。