1. 90前端首页
  2. 前端框架

vue-router源码阅读

前言

为什么读源码?
一、更好地使用api,解决工作中遇到的问题
二、避免一些使用中的常见错误

调用步骤

  • 1.安装router插件
import router form vue-router
Vue.use(router)
  • 2.实例化router
const Home = { template: \'<div>home</div>\' }
let router = new Router({
    routes: [{ path: \'/home\', component: Home }]
});
  • 3.挂载router
new Vue({
    router,
    render(h){
        h(App)
    }
})

安装router插件

Vue.use是Vue提供的一种插件安装机制,如果插件是一个对象,必须提供install方法。Vue.use会调用插件的install方法。router的install方法主要往vue实例上增加了一些生命周期的处理,并且在vue构造器原型上增加了$router和$route的属性,也就是我们调用的时候使用的this.$router及this.$route。响应式的定义了_route,这样在路由对象改变的时候所依赖的视图文件会跟着更新。
以下是vue-router的install实现

import View from \'./components/view\'
import Link from \'./components/link\'

export let _Vue

export function install (Vue) {
  //防止重复安装插件
  if (install.installed && _Vue === Vue) return
  install.installed = true

  _Vue = Vue

  const isDef = v => v !== undefined

  const registerInstance = (vm, callVal) => {
    let i = vm.$options._parentVnode
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }
  //通过全局mixin注入一些生命周期的处理
  Vue.mixin({
    beforeCreate () {
      if (isDef(this.$options.router)) {
        this._routerRoot = this // 定义私有属性_routerRoot,指向Vue实例,用于$router属性和$route属性从中读取值
        this._router = this.$options.router //定义私有属性_router,指向VueRouter实例
        this._router.init(this) //初始化vue-router
        Vue.util.defineReactive(this, \'_route\', this._router.history.current) // 定义私有属性_route,设置为响应式
      } else {
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this, this) // 注册实例
    },
    destroyed () {
      registerInstance(this)  // 销毁实例
    }
  })

   // 在Vue的prototype上初始化$router和$route的getter,让其只读,不可更改。分别指向当前Router实例及当前Route信息
  Object.defineProperty(Vue.prototype, \'$router\', {
    get () { return this._routerRoot._router }
  })

  Object.defineProperty(Vue.prototype, \'$route\', {
    get () { return this._routerRoot._route }
  })

  //全局定义RouterView及RouterLink组件
  Vue.component(\'RouterView\', View)
  Vue.component(\'RouterLink\', Link)
}

实例化router

实例化vue-router类的过程中主要干了两件事,添加matcher属性和根据mode添加history属性。调用createMatcher方法返回一个Matcher对象,里面包含match和addRoutes两个方法。这两个方法主要是供Vue-router上原型方法match和addRoutes使用。match的主要作用是根据参数location获取获取目标路由对象,这个方法主要在每次路由切换的时候transitionTo方法调用。addRoutes的作用动态添加路由配置,实际开发场景中有可能需要根据不同的人员权限输出不同的界面。添加history属性根据mode参数选择实例化的类,mode共有三种参数,在浏览器环境下有两种方式,分别是在HTML5History,HashHistory两个类中实现的.二者均继承自History类。这里才是整个路由的核心部分,路由切换的核心逻辑处理都包含在这部分内容中。

export default class VueRouter {
  constructor (options: RouterOptions = {}) {
    this.app = null
    this.apps = []
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    //创建matcher,一个包含addRoutes和match方法的对象
    this.matcher = createMatcher(options.routes || [], this)

    let mode = options.mode || \'hash\'
    this.fallback = mode === \'history\' && !supportsPushState && options.fallback !== false
    if (this.fallback) {
      mode = \'hash\'
    }
    if (!inBrowser) {
      mode = \'abstract\'
    }
    this.mode = mode

    switch (mode) {
      case \'history\':
        this.history = new HTML5History(this, options.base)
        break
      case \'hash\':
        this.history = new HashHistory(this, options.base, this.fallback)
        break
      case \'abstract\':
        this.history = new AbstractHistory(this, options.base)
        break
      default:
        if (process.env.NODE_ENV !== \'production\') {
          assert(false, `invalid mode: ${mode}`)
        }
    }
  }

实例化vue

new Vue({
    router,
    render(h){
        h(App)
    }
})

在options中传入router和渲染方法,此时创建一个Vue实例,对应的vueRouter的install方法中混合的beforeCreate钩子就会被调用,根据当前的url(history.getCurrentLocation()),完成第一次的路由导航。这也就是为什么我们开发服务启动以后,hash模式下输入http://localhost:8080,url会变成http://localhost:8080/#/。另外监听history对象,如果变化则会更新vue实例的_route属性。

init (app: any /* Vue component instance */) {
    const history = this.history
    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      const setupHashListener = () => {
        history.setupListeners()
      }
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }

    history.listen(route => {
      this.apps.forEach((app) => {
        app._route = route
      })
    })
}

通过代码可以看到VueRouter中实现路由切换使用的是transitionTo方法。这是History类上的一个方法。

总结

本文根据调用vue的步骤大概说了vue-router源码的主要流程,后续会对一些细节的处理及实现进行展开。进行正常调用以后我们使用中的主要逻辑流程如下图

vue-router源码阅读

本文来自网络整理,转载请注明原出处:https://segmentfault.com/a/1190000021592204

展开阅读全文

发表评论

登录后才能评论