vue-router源码分析(下)

简介

路由的概念相信大部分同学并不陌生,我们在用 Vue 开发过实际项目的时候都会用到 Vue-Router 这个官方插件来帮我们解决路由的问题。它的作用就是根据不同的路径映射到不同的视图。本文不再讲述路由的基础使用和API,不清楚的同学可以自行查阅官方文档vue-router3 对应 vue2vue-router4 对应 vue3。今天我们从源码出发以vue-router 3.5.3源码为例,一起来分析下Vue-Router的具体实现。

由于篇幅原因,vue-router源码分析分上、中、下三篇文章讲解。

vue-router源码分析(上)

vue-router源码分析(中)

vue-router源码分析(下)

前面我们已经讲了路由的安装和实例化,下面我们来看看初始化流程。

初始化

前面我们说到,在安装的时候会混入全局mixin。我们知道全局混入,会影响后续创建的所有Vue实例beforeCreate首次触发是在Vue根实例实例化的时候new Vue({router})时。

Vue.mixin({
  beforeCreate () {
    if (isDef(this.$options.router)) {
      this._routerRoot = this
      this._router = this.$options.router
      // 初始化
      this._router.init(this)
      Vue.util.defineReactive(this, '_route', this._router.history.current)
    } else {
      this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
    }
    registerInstance(this, this)
  }
  
  ...
}) 

由于router仅存在于Vue根实例$options上,所以,整个初始化只会被调用一次。也就是这个if (isDef(this.$options.router))只会执行一次。

在这里我们我们重点分析下init方法。

分析init方法

// src/index.js

...

init (app: any /* Vue component instance */) {
  process.env.NODE_ENV !== 'production' &&
    assert(
      install.installed,
      `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
        `before creating root instance.`
    )

  this.apps.push(app) // 保存实例

  // 绑定destroyed hook,避免内存泄露
  app.$once('hook:destroyed', () => {
    const index = this.apps.indexOf(app)
    if (index > -1) this.apps.splice(index, 1)
    // 需要确保始终有个主应用
    if (this.app === app) this.app = this.apps[0] || null

    if (!this.app) this.history.teardown()
  })

  // main app已经存在,则不需要重复初始化history 的事件监听
  if (this.app) {
    return
  }

  this.app = app

  const history = this.history

  if (history instanceof HTML5History || history instanceof HashHistory) {
    const handleInitialScroll = routeOrError => {
      const from = history.current
      const expectScroll = this.options.scrollBehavior
      const supportsScroll = supportsPushState && expectScroll

      if (supportsScroll && 'fullPath' in routeOrError) {
        handleScroll(this, routeOrError, from, false)
      }
    }
    const setupListeners = routeOrError => {
      history.setupListeners()
      handleInitialScroll(routeOrError)
    }
    history.transitionTo(
      history.getCurrentLocation(),
      setupListeners,
      setupListeners
    )
  }

  // 调用父类的listen方法,添加回调; 
  // 回调会在父类的updateRoute方法被调用时触发,重新为app._route赋值
  // 由于app._route被定义为响应式,所以app._route发生变化,依赖app._route的组件(route-view组件)都会被重新渲染
  history.listen(route => {
    this.apps.forEach(app => {
      app._route = route
    })
  })
} 

可以看到,init方法主要做了下面几件事

  1. 检查了VueRouter是否已经安装

  2. 保存了挂载router实例vue实例VueRouter支持多实例嵌套,所以存在this.apps来保存持有router实例vue实例

  3. 注册了一个一次性钩子destroyed,在destroyed时,卸载this.app,避免内存泄露。

  4. 检查了this.app,避免重复事件监听。

  5. 根据history类型,调用transitionTo跳转到初始页面,并调用setupListeners函数初始化路由变化的监听。

  6. 注册updateRoute回调,在route更新时,更新app._route完成页面重新渲染。

我们重点看下transitionTo相关逻辑

分析transitionTo方法

// src/history/base.js

transitionTo (
  location: RawLocation, // 原始location,一个url或者是一个Location
  onComplete?: Function, // 跳转成功回调
  onAbort?: Function // 跳转失败回调
) {
  let route
  try {
    // 传入需要跳转的location和当前路由对象,返回to的Route
    route = this.router.match(location, this.current)
  } catch (e) {
    this.errorCbs.forEach(cb => {
      cb(e)
 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值