一、路由
1.路由介绍
现代的前端应用大多都是 SPA(单页应用程序)single page application,也就是只有一个 HTML 页面的应用程序。因为它的用户体 验更好、对服务器的压力更小,所以更受欢迎。
为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生。
- 单页面应用是指在加载初始页面后,应用程序不再重新加载整个页面,而是通过JavaScript动态地更新页面内容。这意味着页面的状态和URL的变化可以通过JavaScript来管理,而不需要从服务器获取新的页面。
- 路由(或者称为客户端路由)是单页面应用程序的一部分,它允许应用程序在不重新加载整个页面的情况下,根据URL的变化来显示不同的内容。通常,路由通过监听URL的变化,并在不刷新整个页面的情况下,加载不同的视图或组件。
什么是路由?
1.一个路由就是一个映射关系(key:value)
2.key为路径, value可能是function或component
- 前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
- 前端路由是一套映射规则,在React中,是 URL路径 与 组件 的对应关系 。一个路由就是一对映射关系-->key:value
- 使用React路由简单来说,就是配置 路径和组件(配对)
2.前后端路由
- 前端路由:是指在单页面应用程序(SPA)中,通过JavaScript来管理不同URL路径对应的视图或组件的机制。在前端路由中,URL的变化被JavaScript框架(如React Router、Vue Router等)捕获,然后相应地加载或渲染不同的视图,而不需要向服务器请求新的页面。这样可以实现在单个HTML页面中动态切换内容,提供更流畅的用户体验。
- 后端路由:是指服务器端应用程序中定义的URL路径和请求处理的映射关系。当客户端发起HTTP请求时,后端路由将根据请求的URL路径来确定如何处理这个请求,通常包括调用相应的处理函数或控制器来生成并返回响应的数据。后端路由通常用于构建多页面应用(MPA)或者提供API服务。
- 前端路由:
1)浏览器端路由,key是路径,value是component,用于展示页面内容。
2)注册路由: <Route path="/test" component={Test}>
3)工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
- 后端路由:
1)理解: key是路径,value是function, 用来处理客户端提交的请求。
2)注册路由: router.get(path, function(req, res))
3)工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
3.路由模式
路由模式有两种 :hash 和 history
- history模式
使用了 html5的 historyAPI pushState 函数设置 history, 使用 ev.state获取 历史记录 window.onpopstate 的事件完成了路由效果; 支持到 ie10 ;数据不会显示在url地址栏;history必须后台支持,否则会出现404页面;传递的数据会传到后台。刷新页面数据不丢失。
- hash 模式
使用了location.hash 对历史记录进行获取和设置,使用 onhashchange事件 来监听hash是否发生变化;数据会显示在url地址栏,在 # 的后面,#后面的内容不会传到服务器。刷新页面数据丢失。
二、vue路由
1.路由基础
1.1 概念
url地址除了域名,后面跟的那一段路径信息,这个路径会指向页面所在的组件
1.2 使用步骤
-
如果路由表名改为myroutes,创建路由对象要写成routes:myroutes(key是routes不能变,key与值相等写routes即可),如果路由对象名改为myrouter,注册时要写router:myrouter(key是router不能变,当key与值相等写router即可),建议路由表名用routes,路由对象名用router
-
先引入vue.js,再引入vue-router.js,有顺序
-
组件不用在app里注册,认为是个页面,在路由表里写了
vue 路由:vue url地址后面的路径 最终 绑定了哪个页面,
<div id="app">
<!-- 7.路由链接 -->
<!-- 注意: 使用router-link跳转页面,页面是不刷新的目前的项目是单页面开发
router-link 解析成a标签,但和a有区别,页面不用重新加载
-->
<router-link to="/">首页</router-link>
<router-link to="/about">关于我们</router-link>
<!-- 6.路由出口 -->
<router-view></router-view> //多个路由出口才定义name
</div>
<script src="./js/vue.js"></script>
<!-- 1.引入路由插件 -->
<script src="./js/vue-router.js"></script>
<script>
// 2.创建两个页面
let home = {
template: `<div>首页</div>`
}
let about = {
template: `<div>关于我们</div>`
}
// 3.定义路由表
// 每个路由项,有两个属性,1:路径2:路径对应的组件
let routes = [
{ "path": "/", "component": home },
{ "path": "/about", "component": about },
]
// 4.创建路由对象
let router = new VueRouter({
routes
})
var app = new Vue({
el: "#app",
data: {
},
// 5.注册
router
})
</script>
步骤:
1.引入vue-router
2.创建页面
3.配置路由表
4.创建路由对象 new VueRouter
5.注册路由
6.设置路由出口 router-view
7.设置路由链接 router-link 会解析为 a ,但是不会整页刷新
2.路由传参
2.1 动态路由
1.定义路由:在路由表路径后面添加 :key
{path:"/home/:userid",component:home}
2.使用路由:在 router-link
//<router-link to="/home/动态路由的值"></router-link> <router-link to="/home/16"></router-link>
3.在跳转到的页面获取动态路由传过来的数据
- html : {{$route.params.key }} 例 {{$route.params.userid}}
- js: this.$route.params.key 例 this.$route.params.userid
注意:参数会跟在 url后面,刷新页面时,参数还存在
2.2 params
1.使用编程式导航,params方式传参
this.$router.push({"name":"about",params:{userid:66}})
2.接收参数:
- html : {{$route.params.key }} 例 {{$route.params.userid}}
- js: this.$route.params.key 例 this.$route.params.userid
注意:params 参数没有在 url后面,刷新页面时,参数就获取不到了
2.3 query
1.使用编程式导航,query方式传参
this.$router.push({"path":"about",query:{userid:66}})
2.接收参数:
- html : {{$route.query.key }} 例 {{$route.query.userid}}
- js: this.$route.query.key 例 this.$route.query.userid
注意:query参数在 url后面,先拼接 ? 在拼接 query 的key和value,刷新页面时,参数还存在
编程式导航:指通过 JavaScript 代码来实现页面跳转的方式。
// 传递路由参数和查询参数 this.$router.push({ path: '/user/123', // 路径 query: { name: 'John' }, // 查询参数 params: { id: 123 } // 路由参数 });
声明式导航:通过
<router-link>
组件或router-link
指令来实现页面跳转的方式。<!-- 传递路由参数和查询参数 --> <router-link :to="{ path: '/user/123', query: { name: 'John' }, params: { id: 123 } }">User</router-link>
3.路由知识汇总
3.1 404 页面处理
在路由的最后面写一句(*会匹配所有路径,所以放最后)
{path:"*",component:page404}
3.2 路由可以设置name名称
{path:"/about",name:"about",component:about}
3.3 编程式导航
push("路由路径"):可以跳转到任何页面 ,push里面的参数可以 字符串或对象
go(-1):返回上一页
3.4 路由重定向redirect
{path:"/",redirect:"/home"}
3.5 路由懒加载
1.路由懒加载(使用import)
这种方式的懒加载是已经在 webpack中设置好的
{ path: '/home', name: 'home', component: () => import(/* webpackChunkName: "home" */ '../views/home.vue')}
2.异步加载resolve require
{ path: '/home', name: 'home', component: resolve => require(['@/components/home'],resolve) },
3.webpack提供的require.ensure()
{ path: '/home', name: 'home', component: r => require.ensure([], () => r(require('@/components/home')), 'demo') },
3.6 激活状态链接的css样式设置
//路由路径和当前页的路由要完全匹配
a.router-link-exact-active {
color: #f03d37;
}
//路由路径和当前页的路由要部分匹配
a.router-link-active {
color: #f03d37;
}
3.7 $router 和 $route的区别
1.$route 当前页路径
属性: fullpath 、params、query
2.$router 项目的所有路由路径
options.routes 是个数组
函数 :push() 跳转到某一页、go()返回上一页
4.子路由
//1.创建子页
var child1 = {
template: `<div>子页一</div>`
}
var child2 = {
template: `<div>子页二</div>`
}
var routes = [
{ "path": "/", component: about },
{
"path": "/usercenter",
component: usercenter,
//2.在路由表中添加子路由
children: [
{
path: 'child1',
component: child1
},
{
path: 'child2',
component: child2
},
]
},
];
var usercenter = {
template: `<div>
用户中心
<!--4.子路由的路由链接-->
<router-link to="/usercenter/child1">子页一</router-link>
<router-link to="/usercenter/child2">子页二</router-link>
<!--3.添加子页面的路由出口-->
<router-view></router-view>
</div>`
}
5.路由模式
路由模式有两种 :hash 和 history(详情🔍标题一)
const router = new VueRouter({
mode: 'history',
routes: [...]
})
6.路由守卫
分三种 :
全局守卫(全局前置守卫、全局后置钩子、全局解析守卫)
路由独享守卫
组件级守卫(进入组件的守卫、离开组件的守卫、组件更新时的守卫)
-
全局守卫(3种)
①全局前置守卫 (最常用):beforeEach
beforeEach:有一个参数是一个回调函数,回调函数有三个参数,to(路由到哪里去),from (路由从哪里来),next (如果守卫了接下来做什么)
router.beforeEach((to, from, next) => { if (to.path == '/usercenter') {//需要守卫 console.log("守卫了"); //判断登录状态 if (logintype) {//已经登录,用户中心不用守了 next(); } else {//还没有登录,跳转到 登录页 next('/login'); } } else { //放行 next(); } })
②全局后置钩子:afterEach
③全局解析守卫:beforeResolve
- 路由独享守卫
beforeEnter
var routes = [
{ "path": "/", component: about },
{ "path": "/usercenter", component: usercenter },
{ "path": "/login", component: login },
{
"path": "/list",
component: list,
//路由独享守卫 ,只给某一个路由做成守卫处理
beforeEnter(to, from, next) {
console.log("路由独享守卫运行了");
//放行
next();
}
},
];
- 组件级守卫(3种)
①组件进入时的守卫:beforeRouteEnter
②组件离开时的守卫:beforeRouteLeave
③组件更新时的守卫:beforeRouteUpdate(
必须是动态路由修改参数时才能触发)
7.路由元信息
meta路由元信息,在路由跳转时,可以给该路径带过去一些参数
{
path: '/layout',
name: 'layout',
component: layout,
//重定向到子页,子页可在layout页显示
redirect: '/layout/dashboard',
meta: { title: '首页', icon: 'icon-panel', type: '2' },
children: [{
path: 'dashboard',
name: 'dashboard',
meta: { title: '仪表盘' },
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard/index.vue')
}]
}
三、react路由
1.React Router介绍
React Router V5, 于2019年3月21日发布,react-router-v5官网, 本来是要发布4.4的版本的,结果成了5。从4开始,使用方式相对于之前版本的思想有所不同。之前版本的思想是传统的思想:路由应该统一在一处渲染, Router 4之后是这样的思想:一切皆组件
React Router v6 于 2021 年发布,是 React Router 的一个重大更新,引入了一些重要的变化和新特性。
React Router包含了四个包:
包名 | Description |
---|---|
react-router | React Router核心api |
react-router-dom | React Router的DOM绑定,在浏览器中运行不需要额外安装react-router |
react-router-native | React Native 中使用,而实际的应用中,其实不会使用这个。 |
react-router-config | 静态路由的配置 |
主要使用react-router-dom
2.基本原理
React Router甚至大部分的前端路由都是依赖于history.js的,它是一个独立的第三方js库。可以用来兼容在不同浏览器、不同环境下对历史记录的管理,拥有统一的API。
-
老浏览器的history: 通过
hash
来存储在不同状态下的history
信息,对应createHashHistory
,通过检测location.hash
的值的变化,使用location.replace
方法来实现url跳转。通过注册监听window
对象上的hashChange
事件来监听路由的变化,实现历史记录的回退。 -
高版本浏览器: 利用HTML5里面的history,对应
createBrowserHistory
, 使用包括pushState
,replaceState
方法来进行跳转。通过注册监听window
对象上的popstate
事件来监听路由的变化,实现历史记录的回退。 -
node环境下: 在内存中进行历史记录的存储,对应
createMemoryHistory
。直接在内存里push
和pop
状态。
3.核心组件
两种常用 Router : HashRouter 和 BrowserRouter
3.1 HashRouter
作用:包裹整个应用,一个 React 应用只需要使用一次
使用 URL 的哈希值(#)实现(http://localhost:3000/#/first)
HashRouter
是 React Router 中的一种路由器组件,它提供了一种在单页面应用中处理路由的方式。在使用 HashRouter
时,URL 中的哈希部分(#)被用来表示应用程序的不同路由状态,而不会导致整个页面的重新加载。
使用 HashRouter
可以让你在不同的 URL 地址之间切换,而不需要服务器端的支持。这种方式适用于静态网站或者不支持使用 HTML5 History API 的环境。
虽然 HashRouter
在一些情况下可能不如 BrowserRouter
美观和语义化,但它在一些特定的环境中仍然有其用武之地,特别是在需要兼容一些古老浏览器或服务器配置有限的情况下。
3.2 BrowserRouter ( 推荐 )
作用:包裹整个应用,一个 React 应用只需要使用一次
使用 H5 的 history.pushState API 实现(http://localhost:3000/first)
BrowserRouter
是 React Router 中的一种路由器组件,它使用 HTML5 History API 来管理应用程序的路由。与 HashRouter
不同,BrowserRouter
通过浏览器的 History API 来处理 URL 地址的变化,而不需要在 URL 中添加 #
符号。
使用 BrowserRouter
可以让你在单页面应用中实现更加美观和语义化的 URL 地址,而且不会在 URL 中出现 #
符号。这种方式更符合现代 Web 应用的设计和 SEO 要求。
需要注意的是,使用 BrowserRouter
需要在服务器端进行相应的配置,以确保在刷新页面或直接访问特定 URL 时能够正确地返回应用程序的入口点。这通常涉及到在服务器端配置一个通配符路由,以便在任何路径下都返回应用程序的入口 HTML 文件。
3.3 Link
作用 :用于指定导航链接,完成路由跳转
语法说明: 组件通过to属性指定路由地址,最终会渲染为a链接元素
原理:用户点击链接时,阻止默认的页面跳转,而是使用 React Router 提供的路由机制进行页面之间的切换。这样可以实现单页应用的导航而不需要整个页面的刷新。
// to属性:浏览器地址栏中的pathname(location.pathname)
--点击a标签,就会改变浏览器地址栏中的内容了
<Link to="/first">页面一</Link>
3.4 NavLink
作用 :用于指定导航链接,完成路由跳转
语法说明: 组件通过to属性指定路由地址,最终会渲染为a链接元素,继承了
Link
的所有功能,并且在当前页面与链接匹配时,设置activeClassName,从而可以为当前选中的链接应用特定的样式。(v6的NavLink标签移除了activeClassName属性)原理:用户点击链接时,阻止默认的页面跳转,而是使用 React Router 提供的路由机制进行页面之间的切换。这样可以实现单页应用的导航而不需要整个页面的刷新。
// to属性:浏览器地址栏中的pathname(location.pathname)
// activeClassName属性:为当前激活的链接添加特定的样式
--点击a标签,就会改变浏览器地址栏中的内容了
// 在组件中使用 NavLink
<NavLink to="/about" activeClassName="active">About</NavLink>
3.5 Route
作用 :用于定义单个路由的组件,指定导航链接,完成路由匹配
语法说明: path属性指定匹配的路径地址,component(v5)/ element(v6)属性指定要渲染的组件
说明:当url路径为 ‘/about’ 时,会渲染<About/> 组件
// path属性:路由规则
// element:展示的组件
// exact:true是严格匹配,路径必须完全匹配才能渲染组件,false是正常匹配
// Route组件写在哪,渲染出来的组件就展示在哪
<Route path="/" element={<Main />} exact={true}></Route>
<Route path="/first" element={<First />}></Route>
3.6 Routes(v6)
作用 :用于包裹多个
Route
的组件,提供一个路由出口,满足条件的路由组件会渲染到组件内部,定义path和组件的对应关系
// exact:true是严格匹配,路径必须完全匹配才能渲染组件,false是正常匹配
<Routes exact={true}>
<Route path="/" element={<Main />} />
<Route path="/contact" element={<Contact />} />
</Routes>
3.7 Switch(v5)
作用:
<Switch>
是 React Router 库中的一个组件,用于包裹一组<Route>
元素。确保只有一个与当前 URL 匹配的路由会被渲染。一旦找到了与当前 URL 匹配的路由,<Switch>
将停止匹配其他路由并渲染匹配到的路由组件。Switch可以提高路由匹配效率(单一匹配).
function App() {
return (
<Router>
<div>
// 没有匹配的路由,则会渲染 <Route> 中定义的404页面。
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
// 相当于path="/*",所有路径均能匹配,放最后
<Route component={Page404} />
</Switch>
</div>
</Router>
);
}
3.8 Navigate(v6)
作用:用于在 React 应用程序中进行编程式导航。它可以用于在特定条件下或在特定事件发生时,以编程的方式触发路由的变化。
说明:
<Navigate>
组件接受一个to
属性,用于指定导航的目标路径。这个属性的值应该是要导航到的路径字符串。
function MyComponent() {
// 在某些条件下进行编程式导航
const shouldNavigate = true; // 根据具体条件设置是否应该进行导航
if (shouldNavigate) {
return <Navigate to="/new-page" />;
} else {
return <div>不进行导航的内容</div>;
}
}
3.9 Redirect(v5)
作用:
用于在页面渲染时重定向用户到另一个路由。通常情况下,<Redirect>
组件会在某些条件下自动触发,比如用户登录后自动跳转到登录后的页面,或者在用户访问某个特定页面时进行重定向。说明:进入路由页面前的守卫 Redirect
function App() {
...
return (
<Router>
<div>
<Route exact path="/">
{loggedIn ? <Redirect to="/dashboard" /> : <LoginPage onLogin={handleLogin} />}
</Route>
<Route path="/dashboard">
// loggedIn:是否登录,未登录跳转LoginPage页
{loggedIn ? <Dashboard /> : <Redirect to="/" />}
</Route>
// from:来源地址,即想要进入的地址 to:强制跳转的地址
<Redirect from="/*" to="/" /> // 404页面定位到首页
</div>
</Router>
);
}
3.10 Prompt(v5)
作用:用于实现离开组件前的路由守卫,它可以在用户尝试离开当前页面时显示一个确认提示。这个提示通常用于在用户进行导航或关闭页面时提醒用户保存未保存的数据或确认是否真的要离开当前页面。
说明:离开页面前的守卫 Prompt,它有一个必须的属性message,用于给用户提示信息。属性when有两种情况,当它的值是true时,会提示消息。当它的值为false时,不会提示消息。
<Prompt when={true} message={"您确定要离开这个页面么?"} />
3.11 withRouter(HOC)
作用:在一般组件使用路由组件所特有的API 时, 就要借助 withRouter,withRouter可以加工一般组件, 让一般组件具备路由组件所特有的API,withRouter的返回值是一个新组件,简单来说,就是能把一般组件包装成路由组件
说明:withRouter是高阶组件
import React, { Component } from 'react'
import {withRouter} from 'react-router-dom'
class Header extends Component {
back = () => {
this.props.history.goBack()
}
forward = () => {
this.props.history.goForward()
}
go = () => {
this.props.history.go(2)
}
render() {
// console.log('一般组件',this.props)
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
export default withRouter(Header)
- 一般组件与路由组件对比:
1.写法不同:
一般组件: <Demo/>
路由组件: <Route path="/demo" component={Demo}/>
2.存放位置不同:
一般组件: components
路由组件: pages
3.接收到的props不同:
一般组件: 写组件标签时传递了什么,就能收到什么
路由组件: 接收带三个固定的属性
history:
go: ? go(n)
goBack: ? goBack()
goForward: ? goForward()
push: ? push(path, state)
replace: ? replace(path, state)
location:
pathname: "/home"
search: ""
state: undefined
match:
params: {}
path: "/home"
url: "/home"
3.12 总结与对比
两种常用的路由模式:BrowserRouter和HashRouter
HashRouter | 1.路径中有#,例如:localhost:3000/#/demo/test 2.页面刷新,state参数丢失 3.使用的是URL的哈希值 |
BrowserRouter ( 推荐 ) | 1.路径中没有#,例如:localhost:3000/demo/test 2.页面刷新,state参数不丢失,因为state保存在history对象中 3.使用的是H5的history API |
Link | 1.路由跳转,通过to属性指定路由地址,最终会渲染为a链接元素 |
NavLink | 1.路由跳转,通过to属性指定路由地址,最终会渲染为a链接元素 2.设置activeClassName,为当前选中的链接应用特定的样式。(v6移除了activeClassName属性) |
Route | 1.定义单个路由,完成路由匹配 2.属性path:路径地址,属性component(v5)/ element(v6):要渲染的组件 |
Routes(v6) | 包裹多个 |
Switch(v5) | 包裹多个 |
Navigate(v6) | 用于在代码中进行编程式导航,更适合在组件内部根据条件进行动态导航 |
Redirect(v5) | 1.用于在路由匹配时执行重定向操作,更适合在路由配置中静态地执行重定向 2.进入路由页面前的守卫 Redirect |
Prompt(v5) | 1.离开当前页面时显示一个确认提示 2.必须属性message:提示的信息。属性when:true(提示)/false(不提示) 3.离开页面前的守卫 Prompt |
withRouter(HOC) | 1.高阶组件 2.加工一般组件,返回新组件,让其具备路由组件所特有的API |
4.hooks
4.1 useHistory(v5)
作用:在函数式组件中访问和操作路由历史记录。通过
useHistory
,你可以在不同版本的 React Router 中轻松地执行诸如导航、前进、后退和替换历史记录等操作。说明:React Router v6 中,用
useNavigate
实现编程式导航,useHistory被移除了。
//react-router-v5
function MyComponent() {
const history = useHistory();
// 导航到新的路径
const handleNavigate = () => {
history.push('/new-path');
};
// 返回上一页
const handleGoBack = () => {
history.goBack();
};
// 替换当前历史记录
const handleReplace = () => {
history.replace('/another-path');
};
// 其他逻辑...
}
4.2 useParams
作用:在函数式组件中访问 URL 中的参数。当需要从 URL 中提取参数时,
useParams
是一个非常有用的工具。
function UserProfile() {
let { username } = useParams();
return <h2>用户 {username} 的个人资料</h2>;
}
//路由配置
<Route path="/user/:username">
<UserProfile />
</Route>
4.3 useSearchParams(v6)
作用: React Router v6 中引入的新 hook,在函数式组件中获取和操作 URL 查询参数。它允许你读取、写入和删除 URL 查询参数,从而使你能够在应用程序中更轻松地处理与查询参数相关的逻辑。
说明:在 React Router v5 中,没有内置的
useSearchParams
hook。因此,如果需要在 v5 中处理查询参数,直接使用URLSearchParams
API 或者自行编写逻辑来处理查询参数。
function MyComponent() {
const [searchParams, setSearchParams] = useSearchParams();
// 读取查询参数
const queryParamValue = searchParams.get('paramName');
// 写入查询参数
setSearchParams({ paramName: 'paramValue' });
// 删除查询参数
setSearchParams((params) => {
params.delete('paramName');
return params;
});
// 其他逻辑...
}
URLSearchParams:
是 JavaScript 中用于处理 URL 查询参数的内置对象。它提供了一种方便的方式来解析、修改和构建 URL 查询参数。//1. 解析查询参数get // 假设 URL 为 https://www.example.com/?name=John&age=30 const urlParams = new URLSearchParams(window.location.search); console.log(urlParams.get('name')); //2. 添加和修改查询参数set urlParams.set('newParam', 'someValue'); //3. 删除查询参数delete urlParams.delete('name'); //4. 构建新的查询参数append urlParams.append('name', 'Alice'); console.log(urlParams.toString());
4.4 useLocation(v6)
作用:React Router v6 中引入的新 hook,在函数式组件中访问当前 URL 的位置信息,包括路径名(pathname)、搜索参数(search)和哈希值(hash)。
function MyComponent() {
const location = useLocation();
return (
<div>
<h2>当前路径名:{location.pathname}</h2>
<p>搜索参数:{location.search}</p>
<p>哈希值:{location.hash}</p>
</div>
);
}
4.5 useNavigate(v6)
作用:React Router v6 中引入的新 hook,在函数式组件中执行导航操作,类似于v5版本中的
useHistory
。通过useNavigate
,可以在组件中方便地执行页面导航,例如推入新的路径、替换当前路径、后退或前进导航等。
function MyComponent() {
const navigate = useNavigate();
function handleClick() {
// 在点击时执行页面导航
navigate('/new-path');
}
return (
<div>
<button onClick={handleClick}>导航到新路径</button>
</div>
);
}
4.6 useRoutes(v6)
作用:React Router v6 中引入的新 hook,在函数式组件中定义路由配置。
function App() {
const routeConfig = useRoutes({
path: '/',
element: <Home />,
children: [
{ path: 'about', element: <About /> },
{ path: 'dashboard', element: <Dashboard /> },
{ path: 'profile/:id', element: <UserProfile /> },
{ path: '*', element: <NotFound /> }
]
});
return routeConfig;
}
5.创建路由
5.1 使用Router、Switch、Route创建路由(V5)
① 安装依赖
npm install react-router-dom@5
//旧版
②创建路由,switch及404页面处理
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom';
import "./index.css";
import MyFooter from '../components/myfooter';
//BrowserRouter 不带#,NavLink有激活状态链接效果
const Home = () => (<div>首页</div>)
const About = (props) => (<div>关于我们{props.match.params.userid}</div>)
class Cart extends Component {
render() {
console.log(this.props);
return (<div>购物车页面</div>);
}
}
const App = () => (
<Router>
<MyFooter/>
{/* 只完全匹配/ */}
<div>
<Switch>
<Route path={"/"} exact={true} component={Home} />
<Route path={"/about/:userid"} component={About} />
<Route path={"/cart"} component={Cart} />
<Route render={() => <div>404页面</div>} />
<Route path="/:userid" render={() => <div>render页面</div>} />
</Switch>
</div>
</Router>
)
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
③创建底部导航组件MyFooter
src/components/myfooter/index.tsx
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Link } from 'react-router-dom';
import "./index.css";
const MyFooter = () => (
<div>
<Link to={"/"} exact={true} >首页</Link>
<Link to={"/about/666"}>关于我们</Link>
<Link to={{ pathname: "/cart" }}>购物车页面</Link>
</div>
)
export default MyFooter;
5.2 使用Router、Routes、Route创建路由(V6)
① 安装依赖
npm install react-router-dom@6
//新版
② 导入路由的三个核心组件:Router / Routes / Route/Link
Router组件:React Router的根组件,它提供了路由功能的基本环境,可以包裹整个应用程序,一个 React 应用只需要使用一次(BrowserRouter/HashRouter as Router)
Routes组件:是React Router v6中用来定义多个路由的组件。它可以包含多个
Route
元素,并且允许嵌套路由。类似于Switch
组件,但更加灵活,可以更好地支持嵌套路由和布局。Route组件:用于在应用程序中定义单个路由。它根据路径匹配来渲染相应的组件。在React Router v6中,
Route
组件的用法略有变化,不再使用component
属性,而是使用element
属性来指定要渲染的元素。
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom'
③ 使用 Router 组件包裹整个应用(重要)
<Router>
<div className="App">
// … 省略页面内容
</div>
</Router>
④ 用Routes包裹内部Route页面内容
<Routes>
<Route path="/" element></Route>
<Route path="/first" element={<First />}></Route>
</Routes>
⑤ 使用 Link 组件作为导航菜单(路由入口)(Link指定跳转的对应路由组件的路径,to是用来配置路由地址)
<Link to="/first">页面一</Link>
⑥ 使用 Route 组件配置路由规则和要展示的组件(路由出口),在子路由外面用Routes包裹
const First = () => <p>页面一的页面内容</p>
// 使用Router组件包裹整个应用
const App = () => (
<Router>
<div>
<h1>React路由基础</h1>
{/* 指定路由入口 */}
<Link to="/first">页面一</Link>
//路由出口:路由地址对应的组件会在这里进行渲染
<Routes>
//添加一个 “/” 路径的路由,避免警告
<Route path="/" element></Route>
{/* 指定路由出口 */}
//指定路径和组件之间的映射关系。path:路径;element:组件,成对出现
<Route path="/first" element={<First />}></Route>
</Routes>
</div>
</Router>
)
5.3 使用useRoutes钩子创建路由、懒加载(V6)
1.
React.lazy
定义:路由懒加载、组件懒加载 (性能优化),React.lazy
函数能让你像渲染常规组件一样处理动态引入(的组件)。原理就是利用es6 import()
函数。这个import
不是import命令
。同样是引入模块,import命令
是同步引入模块,而import()
函数动态引入。当 Webpack 解析到该语法时,它会自动地开始进行代码分割(Code Splitting),分割成一个文件,当使用到这个文件的时候会这段代码才会被异步加载。(1) 为什么代码要分割
程序越来越大,代码量越来越多。一个页面上堆积了很多功能,也许有些功能很可能都用不到,但是一样下载加载到页面上,所以这里面肯定有优化空间。就如图片懒加载的理论。
(2) import命令与import函数
//import 命令 import { add } from './math'; console.log(add(16, 26)); //import函数 import("./math").then(math => { console.log(math.add(16, 26)); });
⚠️动态
import()
语法参看官网。ES6 入门教程2.Suspense定义:是 React 中的一个组件,它的作用是在组件树中的异步操作(比如代码分割、数据获取等)尚未完成时,展示一个后备内容(fallback),加载指示器或占位内容,以改善用户体验。
import React, { Suspense } from 'react'; const AsyncComponent = React.lazy(() => import('./AsyncComponent')); function ErrorFallback() { return <div>Something went wrong.</div>; } function App() { return ( <div> <h1>My App</h1> <ErrorBoundary FallbackComponent={ErrorFallback}> <Suspense fallback={<div>Loading...</div>}> <AsyncComponent /> </Suspense> </ErrorBoundary> </div> ); }
⚠️在 React 中,
Suspense
通常与React.lazy
和ErrorBoundary
一起使用,以优雅地处理组件的懒加载和错误边界的情况。
① 安装依赖
npm install react-router-dom@6
//新版
②创建router文件夹,该目录下再创建两个文件config.tsx,index.tsx
src/router/config.tsx
// 路由配置
import React from 'react';
// 引入页面
// import Main from '../pages/main';
// import AllList from '../pages/alllist';
// import Category from '../pages/category';
// import Login from '../pages/login';
// import Me from '../pages/me';
// import Cart from '../pages/cart';
// import Item from '../pages/item';
// 懒加载
const Main = React.lazy(() => import('../pages/main'));
const AllList = React.lazy(() => import('../pages/alllist'));
const Category = React.lazy(() => import('../pages/category'));
const Login = React.lazy(() => import('../pages/login'));
const Me = React.lazy(() => import('../pages/me'));
const Cart = React.lazy(() => import('../pages/cart'));
const Item = React.lazy(() => import('../pages/item'));
//路由配置项
const routes = [
{ path: '/', element: <Main />, auth: false, name: '/' },
{ path: '/alllist', element: <AllList />, auth: false, name: '/alllist' },
{ path: '/cart', element: <Cart />, auth: true, name: '/cart' },
{ path: '/category', element: <Category />, auth: false, name: '/category' },
{ path: '/me', element: <Me />, auth: true, name: '/me' },
{ path: '/item/:id', element: <Item />, auth: false, name: '/item' },
{ path: '/login', element: <Login />, auth: false, name: '/login' },
];
export default routes;
src/router/index.tsx
//路由
import { React, Suspense } from 'react';
// BrowserRouter 路由的历史记录模式
//useRoutes 可以获取所有路由
//Navigate 替代了 Redirect 进行守卫跳转
import { BrowserRouter, useRoutes, Navigate } from 'react-router-dom';
import { connect } from 'react-redux';
import MyFooter from '../components/myfooter';
//引入路由配置表
import routes from './config';
// 写封装的路由表
const App = (props) => {
//路由守卫
function guardRoutes(routes) {
console.log(routes);
const list = [];//保存整理好的路由表
const isLogin = props.token !== "" ? true : false;//登录状态 true 表示已经登录了
routes.forEach(route => {
const obj = { ...route };//一条路由信息
if (obj.redirect) {
obj.element = <Navigate to="/login" replace={true} />
}
if (!isLogin) {//没登录
if (obj.auth) {//该页有守卫
obj.element = <Navigate to="/login" replace={true} />
}
}
list.push(obj);
})
return list;
}
const GetRoutes = () => useRoutes(guardRoutes(routes));
return (
<BrowserRouter>
<MyFooter />
<Suspense fallback={<div> Loading... </div>}>
{/* 已经配置好守卫的路由路径 */}
<GetRoutes />
</Suspense>
</BrowserRouter>
)
}
export default connect(state => ({ token: state.user.token || "" }))(App);
③创建底部导航组件MyFooter
src/components/myfooter/index.tsx
import React from 'react';
import { NavLink, useLocation } from 'react-router-dom';
import './index.scss';
import { connect } from 'react-redux';
const MyFooter = (props) => {
// useLocation 相当于 this.props.location
const { pathname } = useLocation();
const pathArr = ['/', '/category', '/cart', '/me', '/alllist']
const { type } = props
const goTop = () => {
document.documentElement.scrollTop = document.body.scrollTop = 0;
}
return (
<>
{
pathArr.indexOf(pathname) !== -1 ? < div className='footer' >
{
type ? <NavLink to="/">首页</NavLink> : <div onClick={goTop}>
返回顶部
</div>
}
<NavLink to="/alllist">全部商品</NavLink>
<NavLink to="/cart">购物车</NavLink>
<NavLink to="/category">分类</NavLink>
<NavLink to="/me">未登录</NavLink>
</div > : ""
}
</>
)
}
export default React.memo(connect(
state => ({ type: state.app.type })
)(MyFooter));
5.4 React-router v5和v6的区别
1.Switch➡️Routes
2.component➡️element
3.Redirect➡️Navigate
4.useHistory➡️useNavigate
5.v6不需要exact属性,exact在v5中是精准匹配,v6 内部算法改变,默认匹配完整路径。
6.v6中,Route先后顺序不再重要,它能够自动找出最优匹配路径。
7.v6 嵌套路由改为相对匹配,不再像 v5 那样必须提供完整路径。
8.v6 目前没有prompt组件阻止不期望的导航。
9.v6不再支持用Route标签包裹子组件,v5支持,v5不包裹子组件时用属性component
// v5 <Switch> <Route path="/about"> <About /> </Route> </Switch> //v6 <Routes> <Route path="/about" element={<About />} /> </Routes>
10.v6中移除了NavLink中的actionclassName属性,在v6中可以使用三元运算符的方式实现这个功能
<NavLink className={navData=>navData.isActive?class.active : ""}
11.v6 新增Outelt组件
<Link to="/home2">子路由</Link> <Outlet></Outlet> // 此组件是一个占位符,告诉 React Router 嵌套的内容应该放到哪里。
参考:React-router v5和v6的区别对比_reactrouter5和6的区别-CSDN博客
6.路由传参
6.1 params参数
- 路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
- 注册路由(声明接收):<Route path="/demo/test/:age" component={Test}/>
- 接收参数:this.props.match.params.age或const { id } = useParams()(函数式组件)
- 备注:可以传递字符串,url上没有问号,页面刷新后,参数还在(因为使用动态路由传递参数时,参数会成为 URL 的一部分,即/demo/test/tom/18)
6.2 search参数
- 路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link> (或<Link to={{ pathname: '/demo/test', search: "?name=tom&age=18" }}>详情</Link>)或this.props.history.push('/demo/test?name=tom&age=18');
- 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
- 接收参数:new URLSearchParams(this.props.location.search).get('name')或new URLSearchParams(useLocation().search).get('name')(函数式组件)
- 备注:可以传递字符串,url上有问号,页面刷新后,参数还在(因为刷新时,url上的参数还在)。获取到的search是urlencoded编码字符串,需要借助querystring解析
6.3 state参数
- 路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>或this.props.history.push('/demo/test', {name:'tom',age:18});
- 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
- 接收参数:this.props.location.state.name或useLocation().state.name(函数式组件)
- 备注:可以传递对象,页面刷新后,参数会丢失(因为路由状态重置,
state
会丢失)
Message 组件代码
import React, { Component } from 'react'
import {Link, Route} from 'react-router-dom'
import Detail from './Detail'
export default class Message extends Component {
state = {
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'},
]
}
render() {
const { messageArr } = this.state;
return (
<div>
<ul>
{
messageArr.map((msgObj)=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递params参数 */}
{/*
<Link
to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}
</Link>
*/}
{/* 向路由组件传递search参数 */}
{/*
<Link
to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}
</Link>
*/}
{/* 向路由组件传递state参数 */}
<Link
to={{pathname:"/home/message/detail",state:{id:msgObj.id,title:msgObj.title}>{msgObj.title}
</Link>
</li>
)
})
}
</ul>
<hr />
{/* 声明接收params参数 */}
{/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}
{/* params参数无需声明接收, 正常注册即可 */}
{/* <Route path="/home/message/detail" component={Detail}/> */}
{/* state参数无需声明接收, 正常注册即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
)
}
}
Detail 组件代码
import React, { Component } from 'react'
// import qs from 'querystring'
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,世界'},
{id:'03',content:'你好,我'}
]
export default class Detail extends Component {
render() {
// 接收params参数
// const {id,title} = this.props.match.params
// 接收search参数
// const {search} = this.props.location
// const {id,title} = qs.parse(search.slice(1))
// 接收state参数
const {id,title} = this.props.location.state
const findResult = DetailData.find((datailObj)=>{
return datailObj.id === id
})
return (
<ul>
<li>id:{id}</li>
<li>title:{title}</li>
<li>context:{findResult.content}</li>
</ul>
)
}
}
7.编程式路由导航
7.1 路由跳转的两种模式 push与replace
上面写的都是 push 模式, 而要使用 replace时, 就在标签上面加上 replace就可以了
{/* 向路由组件传递state参数 */}
<Link replace to={{pathname:"/home/message/detail",state:{id:msgObj.id,title:msgObj.title}>{msgObj.title}
</Link>
7.2 路由跳转、前进、后退
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
- this.props.history.push() 跳转
//将新的路径推入历史堆栈,并且导航到指定的路径。这意味着会向浏览器历史记录中添加一个新条目,并且加载相应的页面。 this.props.history.push('/new-path');
- this.props.history.replace() 替换
//会将当前路径替换为新的路径,而不是向历史堆栈中添加新条目。这意味着当前页面会被替换为新的页面,而不会产生新的历史记录条目。 this.props.history.replace('/new-path');
- this.props.history.goBack() 后退
//在历史记录中后退一步,就像用户点击浏览器的后退按钮一样。 this.props.history.goBack();
- this.props.history.goForward() 前进
//在历史记录中前进一步,就像用户点击浏览器的前进按钮一样。 this.props.history.goForward();
- this.props.history.go() 向前或向后
//用于在历史记录中相对当前页面进行导航,n为正数表示向前导航,n为负数表示向后导航。 this.props.history.go(-2); // 向后导航两步 this.props.history.go(3); // 向前导航三步
import React, { Component } from 'react'
import {Link, Route, repalce} from 'react-router-dom'
import Detail from './Detail'
export default class Message extends Component {
state = {
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'},
]
}
// push查看
pushShow = (id,title) => {
//push跳转+携带params参数
// this.props.history.push(`/home/message/detail/${id}/${title}`)
// push 跳转+携带search参数
// this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
// push 跳转+携带state参数
this.props.history.push(`/home/message/detail`,{id,title})
}
// replace 查看
replaceShow = (id,title) => {
// replace跳转+携带params参数
// this.props.history.replace(`/home/message/detail/${id}/${title}`)
// replace跳转+携带search参数
// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
// push 跳转+携带state参数
this.props.history.replace(`/home/message/detail`,{id,title})
}
back = () => {
this.props.history.goBack()
}
forward = () => {
this.props.history.goForward()
}
go = () => {
this.props.history.go(2)
}
render() {
const { messageArr } = this.state;
return (
<div>
<ul>
{
messageArr.map((msgObj)=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递params参数 */}
{/*
<Link
to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}
</Link>
*/}
{/* 向路由组件传递search参数 */}
{/*
<Link
to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}
</Link>
*/}
{/* 向路由组件传递state参数 */}
<Link
to={{pathname:"/home/message/detail",state:{id:msgObj.id,title:msgObj.title}>{msgObj.title}
</Link>
</li>
<button onClick={()=> this.pushShow(msgObj.id,msgObj.title)}>push查看</button>
<button onClick={()=> this.replaceShow(msgObj.id,msgObj.title)}>repalce查看</button>
)
})
}
</ul>
<hr />
{/* 声明接收params参数 */}
{/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}
{/* params参数无需声明接收, 正常注册即可 */}
{/* <Route path="/home/message/detail" component={Detail}/> */}
{/* state参数无需声明接收, 正常注册即可 */}
<Route path="/home/message/detail" component={Detail}/>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}