路由模式
- hash路由
http://www.abc.com/#/hello
:- 使用window.onHashChange来监听hash值的改变,一旦发生变化就找出此hash值所匹配的组件,进而将组件渲染到页面中。
- url的hash发生变化时,不会向后端发送请求。
实现原理
1)vue-router会通过监听window对象的hashchange事件来捕获URL的变化。
2)当URL的hash值发生变化时,vue-router会根据新的hash值找到对应的路由配置,并将对应的组件渲染到页面中的标签中。
- history路由
http://www.abc.com/hello
:- 通过window.onpopstate来监听路由变化,进而匹配不同的组件来渲染出来。
- 当 URL 发生变化时,浏览器会向服务器发送请求,服务器需要配置相应的路由规则,以确保在刷新页面或直接访问 URL 时能正确响应路由。
实现原理
1)vue-router会通过history.pushState()方法或history.replaceState()方法来改变URL的路径部分,而不会触发页面的刷新。包括forward()、back()、go()三个方法,对应浏览器的前进,后退,跳转操作
2)当URL的路径发生变化时,浏览器会向服务器发送请求,服务器需要配置相应的路由规则来返回对应的页面。
3)当服务器返回对应的页面后,vue-router会将返回的页面渲染到页面中的标签中。
history关于404的处理:首先服务器需要支持配置路由,对于所有路由都会返回一个html文件,需要在vue应用里覆盖所有路由的情况,再给一个404页面。(可以通过node或者nginx服务器配置)
- 优缺点分析
hash配置简单,但是路由不好看,history路由好看,但是配置复杂,需要服务端配置。
路由跳转
- router-link
- this.$router.push():跳转到不同的url,但这个方法会向history栈添加一个记录,点击后退会返回到上一个页面。
- this.$router.replace():同样是跳转到指定的url,但是这个方法不会向history里面添加新的记录,点击返回,会跳转到上上一个页面。上一个记录是不存在的。
- this.$router.go(n):相对于当前页面向前或向后跳转多少个页面,类似 window.history.go(n),n可为正数可为负数。正数返回上一个页面。
- this.router.forward() 前进一步
- this.router.back() 回退一步
导航守卫
-
全局前置守卫
beforeEach((to, from, next) => {})
:按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中,如果不调用next,那么钩子不会被resolve -
全局解析守卫
beforeResolve((to, from) => {})
:获取数据或执行任何其他操作最优位置 -
全局后置钩子
afterEach(to, from)
-
路由独享的守卫
beforeEnter(to, from)
:从一个不同的 路由导航时,才会被触发,只有路由变化才会调用,params、hash、query变化时不会调用 -
组件内的守卫
beforeRouteEnter(to, from)
:- 在渲染该组件的对应路由被验证前调用
- 不能获取组件实例
this
(因为当守卫执行时,组件实例还没被创建) - 可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数
beforeRouteUpdate(to, from)
:- 在当前路由改变,但是该组件被复用时调用,可以访问组件实例this
beforeRouteLeave(to, from)
:- 在导航离开渲染该组件的对应路由时调用,可以访问组件实例this
路由解析流程
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫(2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组
- 实例会作为回调函数的参数传入。
数据获取方式
- 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。watch 路由的参数,以便再次获取数据
created() {
// watch 路由的参数,以便再次获取数据
this.$watch(
() => this.$route.params,
() => {
// 获取数据
this.fetchData()
},
// 组件创建完后获取数据,
// 此时 data 已经被 observed 了
{ immediate: true }
)
},
- 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。我们可以在接下来的组件的 beforeRouteEnter 守卫中获取数据,当数据获取成功后只调用 next 方法
beforeRouteEnter(to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post)) // setData获取数据
})
},
// 路由改变前,组件就已经渲染完了
// 逻辑稍稍不同
async beforeRouteUpdate(to, from) {
this.post = null
try {
this.post = await getPost(to.params.id)
} catch (error) {
this.error = error.toString()
}
},
路由懒加载
也称为动态加载路由,当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效,提高应用的初始加载速度。
vue-router动态导入:
routes: [
{ path: '/users/:id', component: () => import('./views/UserDetails.vue') },
],
component接收一个返回 Promise 组件的函数,Vue Router 只会在第一次进入页面时才会获取这个函数,然后使用缓存数据
三种方式实现:
- Vue异步组件
component: resolve => require(['放入需要加载的路由地址'],resolve)
- ES6的 import()
import Vue from 'vue';
import Router from 'vue-router';
// 下面没有指定webpackChunkName,每个组件打包成一个js文件。
const Foo = () => import('../components/Foo')
// 指定了相同的webpackChunkName,会合并打包成一个js文件。
// const Foo = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/Foo')
// const Aoo = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/Aoo')
...
{path:'/Foo',name:'Foo',component:Foo},
- webpack的 require.ensure()
const Coo = resolve=>{
require.ensure([],()=>{resolve(require('@/components/List'))})
}
...
{path:'/list',name:'List',component:Coo}
- 第一个参数是数组,表明第二个参数里需要依赖的模块,这些会提前加载。
- 第二个是回调函数,在这个回调函数里面require的文件会被单独打包成一个chunk,不会和主文件打包在一起,这样就生成了两个chunk,第一次加载时只加载主文件。
- 第三个参数是错误回调。
- 第四个参数是单独打包的chunk的文件名
保护路由
对于一些表单类型的页面,需要登录后使用,但用户直接访问需要保护的页面也是可以的,直到提交到后端的时候才会报错,交互不友好
通过Vue Router 提供的导航守卫来实现,通过设置路由导航钩子函数的方式添加守卫函数,在里面判断用户的登录状态和权限,从而达到保护指定路由的目的
实现路由
- hash
<ul>
<li><a href="#/home">首页</a></li>
<li><a href="#/about">关于</a></li>
</ul>
<!-- 渲染对应的ui -->
<div id="routerView"></div>
<script>
let routerView = document.getElementById('routerView'); // 获取插入html的dom结构
window.addEventListener('load',onHashchange)
window.addEventListener('hashchange', onHashchange)// 浏览器自带的监听哈希值改变的方法:hashchange
// 控制渲染对应的 UI
function onHashchange() {
// console.log(location.hash);
switch (location.hash) { // location.hash为哈希值
case '#/home':
routerView.innerHTML = 'Home'
return
case '#/about':
routerView.innerHTML = 'About'
return
default:
return
}
}
routerView.innerHTML = 'Home'
</script>
- history
<ul>
<li><a href="/home">首页</a></li>
<li><a href="/about">关于</a></li>
</ul>
<!-- 渲染对应的UI -->
<div id="routerView"></div>
<script>
let routerView = document.getElementById('routerView')
window.addEventListener('DOMContentLoaded', onLoad)
// window.addEventListener('popstate', onPopState) // 浏览器的前进后退能匹配
function onLoad() {
onPopState()
let links = document.querySelectorAll('li a[href]')
// 拦截a标签的默认跳转行为
links.forEach(a => {
a.addEventListener('click', (e) => {
e.preventDefault() // 阻止a标签的href行为
history.pushState(null, '', a.getAttribute('href')) // 跳转
onPopState()
})
})
}
function onPopState() {
// console.log(location.pathname);
switch (location.pathname) {
case '/home':
routerView.innerHTML = '<h2>home page</h2>'
return
case '/about':
routerView.innerHTML = '<h2>about page</h2>'
return
default:
return
}
}
</script>