携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情>>
Router
一、完整导航解析流程
-
导航被触发。
-
在失活的组件里调用 beforeRouteLeave 守卫。
-
调用全局的 beforeEach 守卫。
-
在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
-
在路由配置里调用 beforeEnter。
-
解析异步路由组件。
-
在被激活的组件里调用 beforeRouteEnter。
-
调用全局的 beforeResolve 守卫(2.5+)。
-
导航被确认。
-
调用全局的 afterEach 钩子。
-
触发 DOM 更新。
-
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入
二、导航守卫
全局前置守卫
-
无法访问
this
-
返回值如下:
* false
,取消导航跳转
* 一个路由地址,/login
,{ name: 'login', replace: true }
* next()
router.beforeEach((to, from, next) => {
// 判断是否登录
const token = sessionStorage.getItem('token');
if (!token) {
return '/login';
}
next();
})
全局解析守卫
-
在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用
-
router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置
-
无法访问
this
router.beforeResolve((to, from, next) => {
console.log('beforeResolve')
next()
})
全局后置钩子
-
它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用
-
没有
next
参数 -
第三个参数为
navigation failures
,路由取消原因
router.afterEach((to, from, failure) => {
console.log('afterEach:', to, from, failure)
// 根据路径的深度设置路由过渡动画
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
to.meta.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
})
路由独享的守卫 beforeEnter
-
beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发
-
你也可以将一个函数数组传递给 beforeEnter,这在为不同的路由重用守卫时很有用
const removeQueryParams = () => {};
const removeHash = () => {};
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
// beforeEnter: [removeQueryParams, removeHash],
},
]
组件内的守卫
-
beforeRouteEnter
-
beforeRouteUpdate
-
beforeRouteLeave
export default {
data() {
return {
test: 1
}
},
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !可以通过传一个回调给 next 来访问组件实例
// 是支持给 next 传递回调的唯一守卫
next(vm => {
console.log('通过 `vm` 访问组件实例', vm.test)
})
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
next()
},
beforeRouteLeave(to, from, next) {
// 在导航离开渲染该组件的对应路由时调用
// 它可以访问组件实例 `this`
next()
},
}
三、3种路由传参
1、query
传值
-
刷新页面参数还在
-
url显示:
http://localhost:8080/xxx?id=111&name=张三
// aaa.vue发送
this.$router.push({
path: '/xxx',
query: {
id: 111,
name: '张三'
}
})
// bbb.vue接收
this.$route.query.id; // 111
this.$route.query.name; // 张三
2、params
传值
-
刷新页面参数丢失
-
url显示:
http://localhost:8080/xxx
// aaa.vue发送
this.$router.push({
path: '/xxx',
params: {
id: 111,
name: '张三'
}
})
// bbb.vue接收
this.$route.params.id; // 111
this.$route.params.name; // 张三
3、通过路由的方式传值
-
刷新页面参数还在
-
url显示:
http://localhost:8080/xxx/111/张三
// router.js
{
path: '/xxx/:id/:name?', // ?问号的意思是该参数不是必传项
name: 'xxx',
component: () => import(/* webpackChunkName: "xxx" */'@/views/xxx'),
}
// aaa.vue
this.$router.push('/xxx/111/张三')
// bbb.vue
this.$route.params.id; // 111
this.$route.params.name; // 张三
四、路由实现原理
1、hash模式
<div id="contain"></div>
<div class="menu">
<a href="#/">首页</a>
<a href="#/msg">消息</a>
<a href="#/work">工作台</a>
<a href="#/watch">监管</a>
<a href="#/my">我的</a>
</div>
class Router {
constructor(routes = []) {
this.routes = routes
this.currentHash = ''
this.refresh = this.refresh.bind(this)
window.addEventListener('load', this.refresh)
window.addEventListener('hashchange', this.refresh)
}
getUrlPath(url = '') {
if (url.includes('#')) {
return url.split('#')[1]
}
return '/'
}
refresh(event) {
let newURL = event.newURL
if (!newURL) {
newURL = window.location.hash
}
this.currentHash = this.getUrlPath(newURL)
this.matchComponent()
}
matchComponent() {
const route = this.routes.find(ele => ele.path === this.currentHash)
const container = document.querySelector('#contain');
if (route && container) {
container.innerHTML = route.component
}
}
}
const routes = [
{
path: '/',
component: '<div>我是首页</div>'
},
{
path: '/msg',
component: '<div>我是消息</div>'
},
{
path: '/work',
component: '<div>我是工作</div>'
},
{
path: '/watch',
component: '<div>我是监管</div>'
},
{
path: '/my',
component: '<div>我是个人中心</div>'
},
];
const router = new Router(routes)
2、history模式
<div id="contain"></div>
<div class="menu">
<a href="/">首页</a>
<a href="/msg">消息</a>
<a href="/work">工作台</a>
<a href="/watch">监管</a>
<a href="/my">我的</a>
</div>
const routes = [
{
path: '/',
component: '<div>我是首页</div>'
},
{
path: '/msg',
component: '<div>我是消息</div>'
},
{
path: '/work',
component: '<div>我是工作</div>'
},
{
path: '/watch',
component: '<div>我是监管</div>'
},
{
path: '/my',
component: '<div>我是个人中心</div>'
},
];
// 重写pushState
(function(history){
var pushState = history.pushState;
history.pushState = function(state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state});
}
return pushState.apply(history, arguments);
}
})(window.history)
// 监听路由变化
window.onpopstate = history.onpushstate = function(e) {
// change view
console.log('---:', e)
const path = e.state.path;
const route = routes.find(ele => ele.path === path)
const container = document.querySelector('#contain');
if (route && container) {
container.innerHTML = route.component
}
}
// 点击菜单
const elements = document.getElementsByTagName('a');
for(let i = 0, len = elements.length; i < len; i++) {
elements[i].onclick = function (event) {
event.preventDefault();
const path = event.target.getAttribute('href');
history.pushState({ path }, null, path)
}
}