Vue Router

98 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情>>

Router


一、完整导航解析流程

  1. 导航被触发。

  2. 在失活的组件里调用 beforeRouteLeave 守卫。

  3. 调用全局的 beforeEach 守卫。

  4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。

  5. 在路由配置里调用 beforeEnter。

  6. 解析异步路由组件。

  7. 在被激活的组件里调用 beforeRouteEnter。

  8. 调用全局的 beforeResolve 守卫(2.5+)。

  9. 导航被确认。

  10. 调用全局的 afterEach 钩子。

  11. 触发 DOM 更新。

  12. 调用 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)

    }

}