React-Router路由
路由
后端路由阶段
- 页面中需要请求不同的
路径
内容时, 交给服务器来进行处理, 服务器渲染好整个页面, 并且将页面返回给客户端. - 这种情况下渲染好的页面, 不需要单独加载任何的
js
和css
, 可以直接交给浏览器展示, 这样也有利于**SEO
的优化**. - 缺点:
- 性能限制:后端路由需要将请求发送到服务器进行处理,然后再将响应返回给客户端。这涉及到网络延迟和服务器负载,可能会对应用程序的性能产生影响。特别是在大量请求同时到达时,后端路由可能面临性能瓶颈。
- 扩展性限制:后端路由通常依赖于服务器端的处理和资源,这使得在面对高流量或大规模应用程序时扩展变得更加复杂。需要增加服务器资源或采用负载均衡等技术来应对流量增加,这可能导致高成本和复杂性。
- 开发效率低下:后端路由要求在服务器端编写和维护路由逻辑,这意味着前后端之间需要进行更多的协作和开发工作。此外,由于请求需要经过网络传输,开发和调试的效率可能会受到一定影响。
- 限制前端技术选择:后端路由通常会限制前端开发人员使用特定的服务器端技术栈。前端开发人员需要熟悉服务器端路由和服务器框架,这可能需要额外的学习成本和时间。
- 不适合单页应用程序(SPA):后端路由通常是基于请求-响应模型的,不太适合用于构建单页应用程序(SPA),因为SPA通常需要在客户端进行动态的路由和页面切换。后端路由需要每次路由切换时都向服务器发送请求,这会增加不必要的网络开销和延迟。
前后端分离阶段;
-
前端渲染的理解:
- 每次请求涉及到的静态资源都会从静态资源服务器获取,这些资源包括HTML+CSS+JS,然后在前端对这些请求回来的资源进行渲染;
- 需要注意的是,客户端的每一次请求,都会从静态资源服务器请求文件;
- 同时可以看到,和之前的后端路由不同,这时后端只是负责提供API了;
-
单页面富应用(SPA);
- SPA最主要的特点就是在前后端分离的基础上加了一层前端路由.
- 也就是前端来维护一套路由规则.
-
前端路由的核心是什么呢?
改变URL,但是页面不进行整体的刷新。
URL的hash
前端路由是如何做到URL和内容进行映射呢?
监听URL的改变。
URL的hash
- URL的hash也就是
锚点(#)
,本质上是改变window.location
的href
属性; - 可以通过直接赋值
location.hash
来改变href
, 但是页面不发生刷新; hash
的优势就是兼容性更好,在老版IE中都可以运行,但是缺陷是有一个#
,显得不像一个真实的路径。
<div id="app">
<a href="#/home">home</a>
<a href="#/about">about</a>
<div class="router-view"></div>
</div>
以下是给提供的代码添加了注释:
// 1. 获取 router-view 元素
const routerViewEl = document.querySelector(".router-view");
// 2. 监听 hashchange 事件
window.addEventListener("hashchange", () => {
switch (location.hash) { // 根据当前 hash 值设置页面内容
case "#/home":
routerViewEl.innerHTML = "home";
break;
case "#/about":
routerViewEl.innerHTML = "about";
break;
default:
routerViewEl.innerHTML = "default";
}
});
- 获取名为 "router-view" 的元素,用于显示路由视图。
- 监听浏览器的
hashchange
事件,当hash
值发生变化时,执行回调函数。根据当前的hash
值,设置routerViewEl
元素的内容,以显示对应的页面内容。如果hash
为"#/home"
,显示"home"
;如果 hash 为 "#/about",显示 "about";否则显示 "default"。
注意:该代码是使用 hash
值来实现简单的路由功能,根据不同的 hash
值显示不同的页面内容。然而,现代的前端路由
通常使用 HTML5 History
API 中的 pushState
或 replaceState
方法来修改 URL
,而不是使用 hash。使用 hash 的路由在 URL 上会带有 #
符号,并且可能不支持一些现代的前端路由功能和服务器配置。
HTML5的History
history
接口是HTML5新增的, 它有六种模式改变URL而不刷新页面:replaceState
:替换原来的路径;pushState
:使用新的路径;popState
:路径的回退;go
:向前或向后改变路径;forward
:向前改变路径;back
:向后改变路径;
<div id="app">
<a href="#/home">home</a>
<a href="#/about">about</a>
<div class="router-view"></div>
</div>
// 1. 获取 router-view 元素
const routerViewEl = document.querySelector(".router-view");
// 2. 监听所有的 a 元素
const aEls = document.getElementsByTagName("a");
for (let aEl of aEls) {
aEl.addEventListener("click", (e) => {
e.preventDefault(); // 阻止默认行为,不进行页面跳转
const href = aEl.getAttribute("href"); // 获取链接的 href 属性
console.log(href); // 打印 href
history.pushState({}, "", href); // 将链接添加到浏览器历史记录中
historyChange(); // 执行页面设置操作
})
}
// 3. 监听 popstate 和 go 操作
window.addEventListener("popstate", historyChange);
window.addEventListener("go", historyChange);
// 4. 执行设置页面操作
function historyChange() {
switch(location.pathname) { // 根据当前路径设置页面内容
case "/home":
routerViewEl.innerHTML = "home";
break;
case "/about":
routerViewEl.innerHTML = "about";
break;
default:
routerViewEl.innerHTML = "default";
}
}
- 获取名为 "router-view" 的元素,用于显示路由视图。
- 遍历所有的
<a>
元素,并为每个元素添加点击事件监听器。当点击链接时,阻止默认行为,获取链接的href
属性,并将其添加到浏览器历史记录中。然后执行historyChange
函数,用于根据当前路径设置页面内容。 - 监听浏览器的
popstate
和自定义的go
事件。当浏览器的前进/后退按钮被点击或通过 JavaScript 的go
方法调用时,执行historyChange
函数,用于根据当前路径设置页面内容。 historyChange
函数根据当前路径的不同,设置routerViewEl
元素的内容,以显示对应的页面内容。如果路径为"/home"
,显示"home"
;如果路径为 "/about",显示 "about";否则显示 "default"。
React-Router
- 安装react-router-dom -- npm install react-router-dom
Router的基本使用
- 选择路由模式 BrowserRouter使用history模式 / HashRouter使用hash模式
<HashRouter>
<App />
</HashRouter>
路由映射配置
- Routes:包裹所有的Route,在其中匹配一个路由
- Router5.x使用的是
Switch
组件
- Router5.x使用的是
- Route:Route用于路径的匹配;
path
属性:用于设置匹配到的路径;element
属性:设置匹配到路径后,渲染的组件;- Router5.x使用的是
component
属性
- Router5.x使用的是
exact
:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件;- Router6.x不再支持该属性
{/* 映射关系: path => Component */}
<Routes>
<Route path='/' element={<Home/>}/>
<Route path='/about' element={<About/>}/>
<Route path='/login' element={<Login/>}/>
<Route path='*' element={<NotFound/>}/>
</Routes>
路由配置和跳转
- 路由跳转 --
Link
组件 /NavLink
组件- 通常路径的跳转是使用
Link
组件,最终会被渲染成a
元素; NavLink
是在Link
基础之上增加了一些样式属性to
属性 -- 用于设置跳转到的路径
- 通常路径的跳转是使用
<div className='nav'>
<Link to="/home">首页</Link>
<Link to="/about">关于</Link>
<Link to="/login">登录</Link>
<Link to="/user?name=why&age=18">用户</Link>
</div>
- NavLink的使用
- 需求:路径选中时,对应的a元素变为红色
- 这个时候,我们要使用
NavLink
组件来替代Link
组件:style
:传入函数,函数接受一个对象,包含isActive属性className
:传入函数,函数接受一个对象,包含isActive属性- 事实上在默认匹配成功时,NavLink就会添加上一个动态的
active
class;- 可能出现样式的层叠,也可以自定义class
<NavLink to="/home"
style={({isActive}) => ({color: isActive ? "red": ""})}>首页</NavLink>
<NavLink to="/about"
style={({isActive}) => ({color: isActive ? "red": ""})}>关于</NavLink>
//或者
<NavLink to="home" className={this.getActiveClass}>首页</NavLink>
getActiveClass({isActive}){
return classNames({"link-active": isActive})
}
- 路由重定向 --
Navigate
- Navigate用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中:
<Route path='/' element={<Navigate to="/home"/>}/>
- Not Found页面配置 --
path="\*"
- 用户随意输入一个地址,该地址无法匹配,那么在路由匹配的位置显示
Not Found
页面
- 用户随意输入一个地址,该地址无法匹配,那么在路由匹配的位置显示
<Route path='*' element={<NotFound/>}/>
路由的嵌套
- 路由之间是存在嵌套关系的。
<Outlet>
组件用于在父路由元素中作为子路由的占位元素。
<Route path='/' element={<Navigate to="/home"/>}/>
<Route path='/home' element={<Home/>}>
<Route path='/home' element={<Navigate to="/home/recommend"/>}/>
<Route path='/home/recommend' element={<HomeRecommend/>}/>
<Route path='/home/ranking' element={<HomeRanking/>}/>
</Route>
</Routes>
<div>
<h1>Home Page</h1>
<div className='home-nav'>
<Link to="/home/recommend">推荐</Link>
<Link to="/home/ranking">排行榜</Link>
<button onClick={e => this.navigateTo("/home/songmenu")}>歌单</button>
</div>
{/* 占位的组件 */}
<Outlet/>
</div>
手动路由的跳转
- 通过
JavaScript
代码逻辑进行跳转(比如点击了一个button),那么就需要获取到navigate
对象。 - 在
Router6.x
版本之后,代码类的API都迁移到了hooks
的写法:- 代码跳转,需要通过
useNavigate
的Hook获取到navigate
对象进行操作
- 代码跳转,需要通过
const navigate = useNavigate()
function navigateTo(path) {
navigate(path)
}
return (
<div className='app'>
<div className='header'>
<span>header</span>
<div className='nav'>
{/* 点击按钮时,导航到 "/category" 路径 */}
<button onClick={e => navigateTo("/category")}>分类</button>
{/* 点击按钮时,导航到 "/order" 路径 */}
<span onClick={e => navigateTo("/order")}>订单</span>
<Link to="/user?name=why&age=18">用户</Link>
</div>
<hr />
</div>
<div className='content'>
{/* 映射关系: path => Component */}
<Routes>
<Route path='/category' element={<Category/>}/>
<Route path='/order' element={<Order/>}/>
<Route path='/detail/:id' element={<Detail/>}/>
<Route path='/user' element={<User/>}/>
<Route path='*' element={<NotFound/>}/>
</Routes>
{useRoutes(routes)}
</div>
</div>
)
- 类组件
// 定义一个高阶组件 (HOC),它接收一个组件(WrapperComponent)作为参数
function withRouter(WrapperComponent) {
// 返回一个新的函数组件,接收原始组件的 props 作为参数
return function(props) {
// 使用 React Router 的 useNavigate hook 获取导航函数,
// 它可以用来进行页面跳转
const navigate = useNavigate();
// 使用 React Router 的 useParams hook 获取动态路由参数,
// 例如,在路由 /detail/:id 中,id 就是一个动态路由参数
const params = useParams();
// 使用 React Router 的 useLocation hook 获取 location 对象,
// 它包含了当前URL的信息
const location = useLocation();
// 使用 React Router 的 useSearchParams hook 获取 URL 查询参数,
// 返回一个包含所有查询参数的 URLSearchParams 实例
const [searchParams] = useSearchParams();
// 将查询参数的 entries 转化为一个对象,方便使用
const query = Object.fromEntries(searchParams);
// 将获取到的路由相关的数据组合到一个对象中
const router = { navigate, params, location, query };
// 返回一个新的组件,传递原始的 props 并添加 router prop
return <WrapperComponent {...props} router={router}/>;
}
}
这个代码是一个使用类组件的React Router例子。`Home` 是一个 `PureComponent`,它包含了一些导航链接和一个 `Outlet` 组件。
下面是对代码的解释:
```jsx
// 定义一个类组件 Home,它继承自 React.PureComponent
export class Home extends PureComponent {
// 定义一个方法 navigateTo,用于导航到指定路径
navigateTo(path) {
// 从 props 中获取 router 对象,然后获取其中的 navigate 方法
const { navigate } = this.props.router;
// 调用 navigate 方法,导航到指定路径
navigate(path);
}
// 定义渲染方法
render() {
// 渲染一些导航链接和一个 Outlet 组件
return (
<div>
<h1>Home Page</h1>
<div className='home-nav'>
{/* 使用 Link 组件导航到 "/home/recommend" 路径 */}
<Link to="/home/recommend">推荐</Link>
{/* 使用 Link 组件导航到 "/home/ranking" 路径 */}
<Link to="/home/ranking">排行榜</Link>
{/* 点击按钮时,使用 navigateTo 方法导航到 "/home/songmenu" 路径 */}
<button onClick={e => this.navigateTo("/home/songmenu")}>歌单</button>
</div>
{/* Outlet 组件,它用于显示当前路径对应的子路由组件 */}
<Outlet/>
</div>
)
}
}
在这个组件中,Link
组件用于创建导航链接,Outlet
组件用于渲染子路由。点击按钮时,会调用 navigateTo
方法,该方法会使用 navigate
函数导航到指定路径。
路由参数传递
- 动态路由的方式;
<Link to="/user/9978">用户</Link>
<Route path="/user/:id" element={<User />}></Route>
// 获取动态路由参数 -- 需要通过 useParams 只能在函数式组件中使用
import { useParams } from "react-router-dom";
export function User() {
const params = useParams()
return (
<div>
<h4>User Page</h4>
<h4>id: {params.id}</h4>
</div>
)
}
- search传递参数;
{/* 创建一个链接,点击后会导航到 "/user" 路径,并带有查询参数 "name=coder" 和 "age=19" */}
<Link to="/user?name=coder&age=19">用户</Link>
function MyComponent() {
// 获取 URL 查询参数
const [searchParams] = useSearchParams();
// 将查询参数转换为 JavaScript 对象
const query = Object.fromEntries(searchParams);
// 现在你可以使用 query.name 和 query.age 获取查询参数了
return (
<div>
{/* 显示查询参数 */}
<p>名字: {query.name}</p>
<p>年龄: {query.age}</p>
</div>
);
}
路由的配置文件
- 早期,
Router
并且没有提供相关的API,我们需要借助于react-router-config
完成; - 在
Router6.x
中,为我们提供了useRoutes
API可以完成相关的配置

<div> {useRoutes(routes)} </div>
- 对某些组件进行了
异步加载
(懒加载
),那么需要使用Suspense
进行包裹:
// 在单独的router/index.js文件中
// 路由懒加载/按需加载/异步加载 ?
// 这样暂时不会显示,因为是异步的要单独下载,需要加载一个loading动画React提供的Suspense组件
const Order = React.lazy(() => import("../page/Order"));
const User = React.lazy(() => import("../page/User"));
const router = [
{
path: '/',
element: <Navigate to="/home" />,
},
{
path: "/home",
element: <Home />,
children: []
}
]
export default router
<HashRouter>
<Suspense fallback={<h4>Loading~~~~</h4>}>
<App />
</Suspense>
</HashRouter>
类组件无法直接使用navigate、location等参数,应该如何进行操作?
- 封装
这是一个React中的高阶组件(HOC),它包装另一个组件,并为其提供与路由相关的属性。
import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
// 这是一个接收组件(WrapperComponent)作为参数的函数
function withRouter(WrapperComponent) {
// 它返回另一个接收props作为参数的函数
return function(props){
// 这个钩子函数允许你以编程的方式进行导航。它相当于react-router v5中的history.push。
const navigate = useNavigate()
// 这个钩子函数允许你访问当前动态路由的参数
const params = useParams()
// 这个钩子函数返回当前的位置对象,该对象包含了当前URL的信息
const location = useLocation();
// 这个钩子函数返回一个新的URLSearchParams对象,该对象表示当前URL的查询参数
const [searchParams] = useSearchParams();
// 将URLSearchParams对象转换成普通的JavaScript对象,方便访问
const query = Object.fromEntries(searchParams);
// 将所有这些与路由相关的对象聚合到一个对象中
const router = {navigate, params, location, query}
// 返回WrapperComponent,扩展原始的props,并添加新创建的router对象作为一个属性
return <WrapperComponent {...props} router={router} />
}
}
export default withRouter
这个函数旨在为组件提供方便访问路由相关功能和数据的方式,如导航、URL参数和查询参数。你可以使用这个函数来包装任何需要使用这些功能的组件。
- 使用
这个代码首先从 'react' 中导入了 React
和 PureComponent
,以及从一个名为 "../hoc/with_router" 的文件中导入了 withRouter
高阶组件。
然后定义了一个名为 About
的组件,它继承自 PureComponent
。这个组件有一个名为 navigateTo
的方法,它使用了 router
属性中的 navigate
方法来进行导航。在 render
方法中,它从 router
属性中获取了 query
对象,并在页面上展示了 query.name
和 query.age
的值。
最后,它使用 withRouter
高阶组件包装了 About
组件,并将结果作为默认导出。这样,About
组件就可以使用 router
属性中的 navigate
方法和 query
对象了。
// 从'react'中导入React和PureComponent
import React, { PureComponent } from 'react'
// 从"../hoc/with_router"中导入withRouter高阶组件
import withRouter from "../hoc/with_router"
// 定义一个名为About的PureComponent组件
export class About extends PureComponent {
// 定义一个导航方法,接收一个路径作为参数
navigateTo(path) {
// 从props中解构出router对象,然后从router中解构出navigate方法
const { navigate } = this.props.router
// 调用navigate方法,进行页面导航
navigate(path)
}
// 定义render方法,返回组件的JSX结构
render() {
// 从props中解构出router对象,然后从router中解构出query对象
const { query } = this.props.router
// 返回一个div元素,其中包含一个标题和一个显示query.name和query.age的文本
return (
<div>
<h3>About Page</h3>
<h4>name: {query.name}-age: {query.age}</h4>
</div>
)
}
}
// 使用withRouter高阶组件包装About组件,并导出结果
export default withRouter(About)
这个 About
组件通过 withRouter
高阶组件得到了 router
属性,可以通过它进行导航以及获取查询参数。
转载自:https://juejin.cn/post/7236713712628121655