路由概念
- 你了解什么是路由么?
了解,路由的概念最先来源于服务端,主要是用来描述URL和处理函数之间的映射。目前,路由主要分为两种,前端路由和后端路由。
- 后端路由
http://www.xx.com/login
后端路由又称作服务器端路由,当服务器接收到客户端的请求时,就会根据所请求的URL,来找到相应的映射函数,然后执行函数,把函数的返回值送给客户端。
优点:安全性好,有利于SEO
缺点:服务器压力大,不利于用户体验,代码不好维护。
- 前端路由
前端路由主要描述的是URL和UI之间的映射关系,前端路由的映射函数通常是处理一些dom的显示和隐藏,访问不同的路径显示不同的页面。
优点:访问页面不会重新刷新加载,没有网络延迟,用户体验好.
缺点:使用浏览器的后退前进会重新发送请求,没有合理的利用缓存,还有一点,不利于SEO。
- 如何实现前端路由?
前端路由实现很简单,就是匹配不同的URL路径,进行解析,然后动态的渲染出页面,在这过程中,首先需要监测URL的变化,一旦改变,进行渲染,同时还要保证,只进行UI更改,不引起页面刷新。
- 前端路由实现方案有几种?
主要有两种实现方式:
- hash
- history api
hash路由实现
hash模式的路由,在URL后面会带一个#号,而hash指的是#及后面的那部分内容。
-
URL中的hash只是客户端的一种状态,常用作锚点在页面里进行导航,当页面向服务完全发送请求的时候,hash部分并不会被发送,所以改变hash也不会引起页面刷新。
-
我们可以通过hashchange事件来监听hash的改变,从而动态的渲染出页面。
-
hash值的改变方式有几种:
- 通过浏览器前进后退改变URL
- 改变location.href和loca.hash的值
- 触发带锚点的链接
实现一个简单的hash路由:
1.在html中,指定一个钩子元素routerview,在这里实现不同页面的渲染。同时绑定html标签需要跳转链接的点击事件。
2.实现一个Router,主要作用是,监听hashchange事件,当hash变化时,触发回调函数handler进行处理。
3.在handler函数,处理不同页面的渲染。
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport">
<title>router</title>
<script src='js/jquery.min.js'></script>
</head>
<body>
<div>
<a href="#/">主页</a>
<a href="#/login">login</a>
<a href="#/register">register</a>
<span id="luyou4">userInfo用户中心</span>
</div>
<div id="routerView"></div>
<script type="text/javascript">
var luyou4 = document.getElementById('luyou4')
luyou4.addEventListener('click',function(){
route.push('/userInfo')
})
function Router(routerList){
// 路由列表
this.$routerList = routerList // 路由列表
this.$courrent = this.getState()// 当前路由
this.$routerView = document.getElementById('routerView')//要渲染的节点
this.handler = this.handler.bind(this)
this.handler();// 初次渲染
window.addEventListener('hashchange',this.handler,false)//监听
}
Router.prototype = {
// 处理回调
handler(){
// console.log('hander')
this.$courrent = this.getState()
this.render(this.$courrent)
},
// 重新渲染页面
render(path){
let compentent = this.$routerList.filter((val)=>{
return val.path === path
})
this.$routerView.innerHTML = compentent[0].compentent
},
getState(){
// #号及后面的值,比如 localhost:1234/#/login
// hash = #/login
const hash = window.location.hash
return hash ? hash.slice(1) : '/'
},
push(path){
window.location.hash = '#' + path
},
// 获取 默认页 url
getUrl(path) {
const href = window.location.href;
const i = href.indexOf('#');
const base = i >= 0 ? href.slice(0, i) : href;
return base +'#'+ path;
},
// 替换页面
replace(path) {
window.location.replace(this.getUrl(path));
},
}
let route = new Router([
{
path:'/',
compentent:'index'
},
{
path:'/login',
compentent:'login'
},
{
path:'/register',
compentent:'register'
},
{
path:'/userInfo',
compentent:'userInfo'
},
])
</script>
</body>
</html>
history路由实现
history路由模式实现是基于HTML5,HTML5中新增了两个API,pushState,replaceState。
-
pushState,replaceState改变URL的路径部分,不会引起页面刷新。
-
我们可以通过popstate事件来检测URL的变化。但是pushState,replaceState事件不会触发popstate事件,需要手动触发。
-
history路由模式没有#号,是真实的URL,刷新页面,这种模式会被服务器识别,服务器会对URL的文件路径进行资源查找,找不到就会报404问题,所以,在我们使用history路由模式时,需要与后端进行沟通。
404问题的处理?
配置webpack(开发环境)
historyApiFallback:{
index:'/index.html'//index.html为当前目录创建的template.html
}
配置nginx(生产环境):如果匹配不到静态资源的路径,就将重定向到 index 页面。
location /{
root /data/nginx/html;
index index.html index.htm;
error_page 404 /index.html;
}
window.history及api
- history.back()后退
- history.forward()前进
- history.go()接受一个整数作为参数,移动到该整数指定的页面,比如go(1)相当于forward(),go(-1)相当于back(),go(0)相当于刷新当前页面
- history.pushState()
- history.replaceState()
history.pushState(state,title,url)
此方法向历史记录中写入数据,并不跳转,该方法接受三个参数。
- state:一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。
- title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。
- url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
history.replaceState(state,title,url)
该方法参数与pushstate是相同的,区别是,它修改浏览器历史记录,同样不会触发跳转。
popstate事件
当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。
- 仅仅调用pushState方法或replaceState方法 ,并不会触发该事件
- 用户点击浏览器倒退按钮和前进按钮触发
- 使用 JavaScript 调用back、forward、go方法时会触发。
如何监听 pushState 和 replaceState 的变化呢?
1.重写方法,使用window.dispatchEvent()自定义触发事件,然后使用window.addEventListener()全局监听所定义的触发事件。
2.在每次调用的时候,执行handler处理函数。
实现一个简单的history模式路由
1.在html中,指定一个钩子元素routerview,在这里实现不同页面的渲染。同时绑定html标签需要跳转链接的点击事件。
2.实现一个Router,主要作用是,监听popState事件,当页面path变化时,触发回调函数handler进行处理。因为单独调用pushState,replaceState不会主动触发popState事件,所以我们这里,在调用之后,手动触发handler事件。或者重写pushState,replaceState函数。
3.在handler函数,处理不同页面的渲染。
把html文档丢到nginx,开启服务,才能看到效果,单独运行html会报错,无法正确获取当前的URL。
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport">
<title>router</title>
<script src='js/jquery.min.js'></script>
</head>
<body>
<div>
<a class="route" href="/">主页</a>
<a class="route" href="/login">login</a>
<a class="route" href="/register">register</a>
<span class="route" data-href="/userInfo" id="luyou4">userInfo用户中心</span>
</div>
<div id="routerView">
</div>
<script type="text/javascript">
var _wr = function(type){
var orig = history[type]
return function(){
var rv = orig.apply(this,arguments)
var e = new Event(type)
e.arguments = arguments
//window.dispatchEvent(),是个自定义触发事件。每次调用pushState时,会触发,同时又会触发window的监听pushState事件。
window.dispatchEvent(e)
return rv
}
}
// 重写事件,给它添加监听
history.pushState = _wr('pushState')
history.replaceState = _wr('replaceState ')
function linkAddEvent(){
document.querySelectorAll('.route').forEach(item=>{
item.addEventListener('click',e=>{
let path;
if(!!item['attributes']['href']){
e.preventDefault();
path = item['attributes']['href'].value
} else {
path = item['attributes']['data-href'].value
}
console.log(path)
if(!!(window.history && window.history.pushState)){
route.push(path)
}
})
})
}
linkAddEvent();
function Router(routerList){
// 路由列表
this.$routerList = routerList // 路由列表
this.$courrent = this.getState()// 当前路由
this.$routerView = document.getElementById('routerView')//要渲染的节点
this.handler = this.handler.bind(this)
this.handler();// 初次渲染
window.addEventListener('popstate',this.handler,false)//监听
window.addEventListener('replaceState',this.handler,false)//监听
window.addEventListener('pushState',this.handler,false)//监听
}
Router.prototype = {
// 处理回调
handler(){
// console.log('hander')
this.$courrent = this.getState()
this.render(this.$courrent)
},
// 重新渲染页面
render(path){
console.log('render',path)
let compentent = this.$routerList.filter((val)=>{
return val.path === path
})
this.$routerView.innerHTML = compentent[0].compentent
},
getState(){
const path = window.location.pathname
return path ? path : '/'
},
push(path){
history.pushState(null,null,path)
// this.handler()
},
// replace 页面
replace(path) {
history.replaceState(null, null, path);
// this.handler();
},
// 前进 or 后退浏览历史
go(n) {
window.history.go(n);
},
}
let route = new Router([
{
path:'/',
compentent:'index'
},
{
path:'/login',
compentent:'login'
},
{
path:'/register',
compentent:'register'
},
{
path:'/userInfo',
compentent:'userInfo'
},
])
</script>
</body>
</html>
路由传参
- query
通过?query=query1形式表现在URL上。
使用path和name都可以。
- params
不能使用path,只能用name。
刷新数据就会消失。可以用localstorage存储。
用于动态路由。
路由守卫
守卫的作用:主要用来通过跳转或取消的方式守卫导航。
有点绕口,简单来说,导航守卫就是路由跳转过程中的一些钩子函数,路由跳转是一个大的过程,类似于生命周期,而其中有许多函数,可以做一些其他的事。
路由守卫有三种:全局守卫,路由独享守卫,组件守卫。
路由的生命周期
-> 导航被触发
-> beforeRouteLeave()当前导航的组件守卫
-> 进入另一个路由
----> beforeCreate ----vue 生命周期
-> beforeEach() 全局前置守卫
-> beforeEnter()路由独享守卫
-> beforeRouteEnter()组件内守卫
-> beforeResolve()全局解析守卫
-> afterEach()全局后置守卫
----> mounted ----vue 生命周期
回调参数(to,form,next)介绍
先来解说回调参数。
to:即将要进入的目标路由对象。
from:即将要离开的路由对象。
next:涉及到next参数的钩子,必须要调用next()这个方法来来resolve这个钩子,否则路由中断,不再继续往下执行。
next(): 进入下一个钩子。next(false):中断当前导航。next(path):跳转到另一个地址。next(error):如果传入的是一个Error实例,导航终止,并将错误传递给回调函数。
全局守卫
全局守卫是在路由实例上直接操作的钩子函数,所有路由配置的组件都会触发。它有三个钩子函数:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
router.afterEach((to, from) => {
// ...
})
-
beforeEach(to,from,next):全局前置守卫,主要用于验证登录。 -
beforeResolve(to,from,next):全局解析守卫,在路由被确认之前,同时所有组件内守卫和异步路由被解析之后调用。 -
afterEach(to,form):全局后置守卫,在路由跳转完成之后触发,不会接受next也不会改变导航本身。
路由独享守卫
路由独享守卫是配置在单个路由时设置的钩子。
let routes = [
{
path:'/user',
component:User,
beforeEnter:(to,form,next)=>{
//do something……
}
}
]
beforeEnter(to,form,next):和beforeEach一致,在beforeEach之后执行。
组件守卫
组件守卫是在组件内执行的钩子函数。
export default{
data(){
//...
},
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
-
beforeRouteEnter(to,form,next):该守卫内访问不到组件实例,this为undefined,因为它在组件生命周期beforeCreate阶段触发。 -
beforeRouteUpdate(to,form,next):在当前路由改变时,并且该组件被复用时调用,可以通过this访问实例。 -
beforeRouteLeave(to,form,next):导航离开该组件的对应路由时调用,可以访问组件实例this。这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过next( false )来取消。
参考