一、认识路由
1、生活中的路由器
构造
:路由器上有多个网线接口,每一个接口都对应一台设备
功能
:多台设备能同时上网
2、编程中的路由和路由器
(1)后端渲染和后端路由
①概念
早期的网站开发整个HTML页面是由服务器来渲染的。在浏览器输入url地址,url会发送到服务器,服务器会对url进行匹配,并且最后交给一个Controller进行处理(html+css+java),最终生成HTML或者数据,返回给前端进行展示。
②优点
渲染好的页面不,需要单独加载任何的js和css ,可以直接交给浏览器展示 ,这样也有利于SEO的优化。
③缺点
(1)整个页面的模块由后端人员来编写和维护的。前端人员如果要开发页面,需通过PHP和Java等语言来编写页面代码。
(2)通常情况下HTML代码和数据以及对应的逻辑会混在一起 ,编写和维护都比较麻烦。
(2)前端渲染和前端路由
①概念
随着Ajax的出现,有了前后端分离的开发模式。后端只提供API来返回数据,前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页面中。浏览器中显示的网页中大部分内容都是由前端写的js代码在浏览器中执行,最终渲染出来的网页。
②优点
前后端责任的清晰,后端专注于数据上,前端专注于交互和可视化上。并且当移动端(iOS/Android)出现后,后端不需要进行任何处理,依然使用之前的一套API即可。
在前后端分离的时候一个应用有很多的html页面(一个url对应一套html+css+js),每进入一个页面就发送一次网络请求到服务器拿资源,在前后端分离的基础上加了一层前端路由,输入一个url从静态服务器上把整个应用的资源全部下载到浏览器(一开始不会全部执行),当点击应用中的某个导航栏后跳转新页面会从全部的资源中抽离出来需要显示的内容(对应的组件)来执行,不会去请求服务器。
目的
:为了实现SPA(single page web application)应用(单页面应用)
定义
:路由就是一组key-value的对应关系(key对应路径,value对应组件),多个路由需要经过路由器管理
工作过程
:当浏览器的路径改变时,对应的组件就会显示
SPA的理解
整个应用只有一个完整的页面(index.html)
点击页面的导航链接不会刷新页面,只会做页面的局部更新
数据需要通过ajax请求获取
一般的SPA应用分为导航区和展示区,点击导航区的导航,引起历史记录里面路径的变化,被前端路由器监测到进行匹配组件,从而展示。
在BOM浏览器对象上有一个history
属性,专门用来管理浏览器的路径、历史记录等
浏览器的历史记录是一个栈结构,栈顶的那条记录就是当前显示的
<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
<script type="text/javascript">
// 直接使用H5推出的history身上的API
let history = History.createBrowserHistory()
// hash值(锚点)
let history1 = History.createHashHistory()
function push (path) {
history.push(path)
return false
}
// 替换栈顶那条记录
function replace (path) {
history.replace(path)
}
// 回退
function back() {
history.goBack()
}
// 前进
function forword() {
history.goForward()
}
// 路径发生变化就会执行
history.listen((location) => {
console.log('请求路由路径变化了', location)
})
</script>
二、路由的使用
效果:点击导航展现内容
点击About导航栏高亮并且展示About组件内容
点击Home导航栏高亮并且展示Home组件内容
原生html中靠<a/>
跳转不同的页面
<a href="./about.html">About</a>
<a href="./home.html">Home</a>
(一)Vue路由
(1) 安装
npm i vue-router3
注意
:vue-router4只能在vue3中使用,vue-router3才能在vue2中使用,如果把vue-router4安装在vue2中会报错
(2) 借助<router-link>
标签实现路由的切换
<router-link to="/about">About</router-link>
<router-link to="/home">Home</router-link>
<router-link />
最终由vue-router
转为<a/>
(3)元素被激活时候的样式active-class
<router-link active-class="active" to="/about">About</router-link>
<router-link active-class="active" to="/home">Home</router-link>
active-class
默认的样式名为active
。如果想要加的样式名为active
,则active-class
可以不写。
(4)路由器的视图,指定组件的呈现位置
<router-view></router-view>
(5)创建路由器
在router
文件夹的index.js文件中
import VueRouter from 'vue-router'
import About from '../components/About'
import Home from '../components/Home'
//创建并暴露一个路由器
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
(6)应用vue-router
vue-router
是一个插件库,在使用的时候需要use
一下然后进行挂载,在main.js
中
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import router from './router'
Vue.config.productionTip = false
Vue.use(VueRouter)
new Vue({
el:'#app',
render: h => h(App),
router:router
})
常见的写法,也可以在router
文件夹的index.js文件中use
,在main.js
文件中引入route
直接挂载即可
(7)路由组件与一般组件的区别
<About />
与<Home />
都是路由组件
,它与一般组件有如下不同点
(1)写法不同
一般组件:需要引入跟注册,写了<Demo/>
标签
路由组件:点击路由链接,修改了路径,前端路由器监测到了,匹配规则的时候一个路径对应一个组件,然后把组件渲染出来放到指定的位置
(2)存放位置不同
一般组件:components
路由组件:pages
(3)组件实例对象上的属性不同
一般组件
路由组件:多了两个属性($route
、$router
)
注意
:通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。每个组件都有自己的$route
属性,里面存储着自己的路由信息。整个应用只有一个router,可以通过组件的$router
属性获取到。
(8)路由的重定向redirect
在router
文件夹中的index.js中配置路由
{ path: '/', redirect: '/homePage'}
(9)路由的懒加载
https://blog.csdn.net/m0_46613429/article/details/128420980
(10)嵌套路由
在Home组件中注册News和Message组件
路由器router
文件中引入子组件并注册
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News,
},
{
path:'message',
component:Message,
}
]
}
]
})
注意
:子组件的path
不需要加/
,因为底层在遍历的时候发现是子路由会自动加/
点击导航链接跳转,错误写法:
<router-link to="/news">News</router-link>
<router-link to="/message">Message</router-link>
点击News导航栏,路径变为了http://localhost:8080/#/news
,组件没有展示,因为没有对应的规则
<router-link to="/home/news">News</router-link>
<router-link to="/home/message">Message</router-link>
(11)路由组件传参
https://blog.csdn.net/m0_46613429/article/details/121774790
(12)路由跳转的两种模式
对浏览器的历史记录进行操作的两种方式:push
是压栈操作,会留下历史记录;replace
会替换掉栈顶的记录。默认的路由跳转使用的是push
模式
开启replace
模式
<router-link replace to="/about">About</router-link>
<router-link replace to="/home">Home</router-link>
(13)编程式路由导航
https://blog.csdn.net/m0_46613429/article/details/121776369
(14)缓存路由组件<keep-alive />
通过切换,“隐藏”了的路由组件,默认是被销毁掉的。有时候想切走之后再切回来,能够保留之前的东西。
例如用户在登录注册时输入了一些基本信息,不小心切换到了其他路由再切回来,原来输入的内容还在,可以提高用户体验感
<keep-alive >
<router-view></router-view>
</keep-alive>
只要在<router-view></router-view>
区域所展示的组件都会被缓存
但是不是所有的组件都有必要缓存,如果一个组件要展示的内容不变或者组件中的数据需要实时跟后台同步,如果被缓存就可以不会发送ajax请求,也就不会实时更新页面。
缓存一个路由组件
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
如果有多个需要缓存,可以写成数组的形式
<keep-alive :include="['News','Message']">
<router-view></router-view>
</keep-alive>
除了指定的路由组件,其他的都被缓存
<keep-alive exclude="News">
<router-view></router-view>
</keep-alive>
要保存着当时真实DOM里面数据的组件缓存下来
①加一个include
,只有匹配的组件会被缓存
②加一个exclude
,任何匹配成功的组件都不会缓存
作用
:让不展示的路由组件保持挂载,不被销毁。
注意
:写在include
和exclude
里面的是组件名,不是路由名,值可以是字符串或者正则表达式
(15)路由组件所独有的两个生命周期勾子:activated 与 deactivated
当被缓存的路由组件中有某个场景需要在组件销毁的时候做一些事情,例如清除定时器
组件挂载的时候开启定时器,组件销毁前清除定时器,常规的写法如下:
mounted(){
this.timer = setInterval(() => {
console.log('@')
},1000)
},
beforeDestroy() {
clearInterval(this.timer)
}
但是由于组件被缓存了,离开页面的时候依然保持挂载不被销毁,就不会触发beforeDestroy
,定时器也不会被清除了
这时需要用到下面两个新的路由组件所独有的
生命周期勾子
// 路由组件被激活时触发
activated(){
this.timer = setInterval(() => {
console.log('@')
},1000)
},
// 路由组件失活时触发
deactivated() {
clearInterval(this.timer)
}
作用
:用于捕获路由组件的激活状态
(16)全局前置路由守卫beforeEach
正常情况下,点击路由导航就会跳转到对应的路由组件。但是有些特殊的时候,不是所有的导航项都能随便点,得符合某些要求才能点,也就是权限的问题。
router.beforeEach((to,from,next)=>{
console.log('前置路由守卫',to,from)
if(to.path == ???){
if(???){
next()
}else{
doSomething...
}
}else{
next()
}
})
初始化的时候to
与 from
里面的东西
当点击Home路由导航时
!!!
初始化和每次路由切换之前router.beforeEach((to,from,next) => {})
被调用,调用了next()
才会放行往下走
真实项目开发中的应用
:
如果进入的是登录页就放行,如果不是登录页,再判断当前缓存中有没有token,如果有就放行,如果没有就回到登录页
router.beforeEach((to, from, next) => {
if (to.path !== '/login') {
if (sessionStorage.getItem("token")) {
next();
} else {
next({path: '/login'})
}
} else {
next();
}
});
(17)全局后置路由守卫afterEach
router.afterEach((to,from)=>{
console.log('后置路由守卫',to,from)
doSomething...
})
!!!
初始化与每次路由切换之后被调用
真实项目开发中的应用
:
使用NProgress来显示在页面顶部的进度条https://blog.csdn.net/m0_46613429/article/details/128406831
(18)独享路由守卫beforeEnter
某一个路由所独享的,写在路由规则配置里面
{
name:'Message',
path:'/message',
component:Message,
meta:{title:'消息'},
beforeEnter: (to, from, next) => {
console.log('独享路由守卫',to,from)
if(???){
doSomething...
}else{
next()
}
}
}
注意
:独享路由守卫只有前置的没有后置的,但是它可以和全局后置路由守卫等随意搭配使用
(19)组件内路由守卫beforeRouteEnter 与 beforeRouteLeave
在组件内部写
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
if(???){
doSomething...
}else{
next()
}
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
if(???){
doSomething...
}else{
next()
}
}
(20) history
模式 与 hash
模式
vue中默认开启的工作模式就是hash
模式
如果要更改就在router
文件夹的index.js中
const router = new VueRouter({
routes,
mode:'history'
})
(1) history
模式:
① path的路径中没有
#
,美观。
② 兼容性和hash模式相比略差。
③ 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
如:http://localhost:8080/info/demo/test
服务器中请求的资源是/info/demo/test
,该资源在服务器上找不到就会返回404错误码
项目打包上线:前端的静态资源部署到服务器上
在项目终端执行命令:npm run build
,打包成功后生成一个dist
文件夹,里面包含一些静态资源如下:
项目初始化路径为:http://localhost:8080/
,点击页面中的路由导航,浏览器中的地址发生变化http://localhost:8080/info/demo/test
,但是不会发送网络请求,点击浏览器的刷新按钮就报404
了。
解决404问题需要跟后台人员配合。
(2) hash
模式:
① path的路径中有
#
,不美观。
#
号后面的都是哈希值
② 兼容性较好。
③
#
后面的hash值不会随着HTTP 请求带给服务器。
如:http://localhost:8080/info/#/demo/test
服务器中请求的资源是/info/
,该资源在服务器上找得到就会返回给前端
(二)React路由
react-router-dom
1、是react的一个插件库
2、专门用来实现一个SPA应用
3、基于react的项目基本都会用到此库
react中靠路由链接实现切换组件
(1)安装
npm i react-router-dom
(2)借助<Link />
标签实现路由的切换
import { Link,NavLink } from 'react-router-dom'
错误写法:
<Link to="/about">About</Link>
<Link to="/home">Home</Link>
路由链接需要由路由器包裹
错误写法:
<Router>
<Link to="/about">About</Link>
<Link to="/home">Home</Link>
</Router>
要具体指明哪一种路由器(BrowserRouter、HashRouter)
import { BrowserRouter} from 'react-router-dom'
<BrowserRouter>
<Link to="/about">About</Link>
<Link to="/home">Home</Link>
</BrowserRouter>
<Link />
最终由react-router-dom
转为<a/>
如果路径为/about就展示About组件,如果路径是/home就展示Home组件
(3)元素被激活时候的样式
该元素被激活的时候追加样式用<NavLink />
标签中的activeClassName
属性
<BrowserRouter>
<NavLink activeClassName="active" to="/about">About</NavLink>
<NavLink activeClassName="active" to="/home">Home</NavLink>
</BrowserRouter>
activeClassName
默认的样式名为active
。如果想要加的样式名为active
,则activeClassName
可以不写。
注意
:标签体内容是一个特殊的标签属性,可以直接写在标签属性中,用children
接收
通过this.props.children
可以获取标签体内容
<BrowserRouter>
<NavLink activeClassName="active" to="/about" children="About"></NavLink>
<NavLink activeClassName="active" to="/home" children="Home"></NavLink>
</BrowserRouter>
(4)注册路由
import { Route } from 'react-router-dom'
错误写法:
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
路由需要由路由器包裹
import { BrowserRouter,Route } from 'react-router-dom'
<BrowserRouter>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<BrowserRouter>
页面不报错,点击对应的导航链接,地址栏对应的路径也发生变化,但是内容展示区没有改变
原因
: <Link />
和<Route />
外面都包裹了一个<BrowserRouter/>
,但是整个应用都得用一个路由器去管理,如上用到了两个路由器,<Link />
发生的变化通知到了包裹的路由器,但是路由器之间不能进行沟通。
解决方法
:整个应用用一个路由器管理
<Link to="/about">About</Link>
<Link to="/home">Home</Link>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
注意
:正常情况下,一个路径对应一个组件。但如果写相同的路径不同的组件,同一个路径的多个组件也能够展示出来,但是路由匹配是按照注册的顺序来的,匹配到了一个还会继续往下匹配,就会出现效率问题。
解决方法
:用<Switch />
组件包裹路由组件
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Switch>
用了<Switch />
匹配到了一个组件之后就不会往下匹配了(单一匹配),可以提高路由匹配效率。
在index.js
中直接把<App />
进行包裹
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root'))
(5)路由组件与一般组件的区别
<About />
与<Home />
都是路由组件
,它与一般组件有如下不同点
(1)写法不同
一般组件:<Demo/>
路由组件:<Route path="/demo" component={Demo}/>
(2)存放位置不同
一般组件:components
路由组件:pages
(3)接收到的props不同
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性(history、location、match)
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/about"
search: ""
state: undefined
match:
params: {}
path: "/about"
url: "/about"
(6)解决多级路径刷新页面样式丢失的问题
index.html中引入了样式,样式写在了public
文件夹下的css文件夹中,如下:
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="stylesheet" href="./css/bootstrap.css">
<Link to="/study/about">About</Link>
<Link to="/studey/home">Home</Link>
<Switch>
<Route path="/study/about" component={About}/>
<Route path="/studey/home" component={Home}/>
</Switch>
描述
:首次打开页面发送网络请求拿到样式,点击导航栏可以正常切换组件,没有发送网络请求。点击刷新按钮,发送网络请求,整个页面的样式丢了。
(1)正常情况下的网络请求路径:http://localhost:3000/css/bootstrap.css
(2)刷新后的网络请求路径:http://localhost:3000/study/css/bootstrap.css
扩展
: http://localhost:3000
是脚手架内置的服务器,通过webpack的devServer开启的,并且内部配置了public
文件夹就是内置服务器的根路径。如果请求的资源不存在,就会返回public
文件夹中的index.html
原因
:正常访问http://localhost:3000/css/bootstrap.css
就是在访问public
文件夹下的/css/bootstrap.css可以正常返回,访问http://localhost:3000/study/css/bootstrap.css
就是在访问public
文件夹下的/study/css/bootstrap.css找不到这个资源就返回index.html
所以失去样式。
解决方法
:
(1)public/index.html 中 引入样式时不写 ./ 写 / (常用)
./是相对路径,以当前文件出发去找,/就会直接去public下找
(2)public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
%PUBLIC_URL%代表的就是public的绝对路径
(3)使用HashRouter
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter } from 'react-router-dom';
import App from './App'
ReactDOM.render(
<HashRouter>
<App />
</HashRouter>,
document.getElementById('root'))
http://localhost:3000/#/
#后面的东西都是hash值,认为是前端资源,不会带给3000这台服务器,所以刷新发网络请求的时候不会把path路径带过去
(7)路由的严格匹配与模糊匹配
<Link to="/about">About</Link>
<Link to="/home/a/b">Home</Link>
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</Switch>
Home组件需要的是/home/a/b
,但匹配的是/home
,页面能正常展示
<Switch>
<Route path="/about" component={About}/>
<Route exact={true} path="/home" component={Home}/>
</Switch>
exact
为true开启严格匹配,页面不再展示内容
注意
:默认使用的是模糊匹配(【输入的路径】必须包含要【匹配的路径】,且顺序要一致);严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
(8)路由的重定向<Redirect />
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
注意
:一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
(9)嵌套路由
<NavLink to="/home">Home</NavLink>
<NavLink to="/about">About</NavLink>
<Switch>
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
<Redirect to="/about"/>
</Switch>
在Home组件中注册News和Message组件
错误写法:
<NavLink to="/news">News</NavLink>
<NavLink to="/message">Message</NavLink>
<Switch>
<Route path="/news" component={News}/>
<Route path="/message" component={Message}/>
<Redirect to="/news"/>
</Switch>
注册路由有先后顺序,Home和About组件是先注册的,News和Message组件是后注册的,每当点击一个路由链接要改地址的时候,它都从最开始注册的那个路由进行逐层匹配,点击News会从最先注册的那两个组件开始找,结果没找到就会Redirect
到about找到About组件。
注意
:注册子路由时要写上父路由的path值。路由的匹配是按照注册路由的顺序进行的
<NavLink to="/home/news">News</NavLink>
<NavLink to="/home/message">Message</NavLink>
<Switch>
<Route path="/home/news" component={News}/>
<Route path="/home/message" component={Message}/>
<Redirect to="/home/news"/>
</Switch>
点击News更改路径为/home/news
,从最先注册的那两个组件开始找,由于模糊匹配找到了Home组件的路径/home
,匹配成功就展示Home组件。如果给Home组件开启严格模式,路径就匹配不上,组件就失效找不到了。
(10)路由组件传参
https://blog.csdn.net/m0_46613429/article/details/121774790
(11)路由跳转的两种模式
对浏览器的历史记录进行操作的两种方式:push
是压栈操作,会留下历史记录;replace
会替换掉栈顶的记录。默认的路由跳转使用的是push
模式
开启replace
模式
<NavLink replace to="/about">About</NavLink>
<NavLink replace to="/home">Home</NavLink>
(12)编程式路由导航
https://blog.csdn.net/m0_46613429/article/details/121776369
(13)让一般组件具备路由组件所特有的API:withRouter()
路由组件接收到三个固定的属性(history、location、match),如果在一般组件中去使用就会报错,因为不存在。
例如在一般组件中用编程式路由导航中的方法做跳转等
解决方法
:
import {withRouter} from 'react-router-dom'
class Demo extends Component {
back = ()=>{
this.props.history.goBack()
}
forward = ()=>{
this.props.history.goForward()
}
go = ()=>{
this.props.history.go(-2)
}
render() {
console.log(this.props)
}
}
export default withRouter(Demo)
withRouter
可以加工一般组件,让一般组件具备路由组件所特有的API,返回值是一个新组件。
(14) BrowserRouter
模式 与 HashRouter
模式
1.底层原理不一样
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。 HashRouter使用的是URL的哈希值。
2.path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响
(1).BrowserRouter没有任何影响,因为state保存在history对象中。 (2).HashRouter刷新后会导致路由state参数的丢失!!!
4.备注:HashRouter可以用于解决一些路径错误相关的问题。