vue-router源码分析(三)

204 阅读1分钟

  上一篇我们分析了vue-router通过confirmTransition函数去执行导航守卫,我们也知道vue-router是靠<router-link>和<router-view>组件完成跳转和渲染的。本次我们就来简要分析一下其中<router-view>渲染和<router-link>跳转的原理是什么。
  以下是这篇文章的大体思路:

  我们先来看一下<router-view>的render函数,只截取了其中比较关键的逻辑:

render (_, { props, children, parent, data }) {
    ···
    const h = parent.$createElement
    const name = props.name
    const route = parent.$route
    const cache = parent._routerViewCache || (parent._routerViewCache = {})

    // determine current view depth, also check to see if the tree
    // has been toggled inactive but kept-alive.
    let depth = 0
    let inactive = false
    while (parent && parent._routerRoot !== parent) {
      const vnodeData = parent.$vnode ? parent.$vnode.data : {}
      if (vnodeData.routerView) {
        depth++
      }
      if (vnodeData.keepAlive && parent._directInactive && parent._inactive) {
        inactive = true
      }
      parent = parent.$parent
    }
    data.routerViewDepth = depth
    const matched = route.matched[depth]
    ···
    const component = cache[name] = matched.components[name]

    data.registerRouteInstance = (vm, val) => {
      // val could be undefined for unregistration
      const current = matched.instances[name]
      if (
        (val && current !== vm) ||
        (!val && current === vm)
      ) {
        matched.instances[name] = val
      }
    }

    ···

    return h(component, data, children)
  }

  首先获取到当前路径信息route,然后通过遍历获取到当前的路由深度,进而找到该渲染的组件。

  下面定义的注册路由实例的方法是在vue-router源码分析(一)中介绍的,在vue组件的beforeCreate和destroyed钩子函数中所执行的registerInstance方法,把当前的vue组件赋值给匹配的路由。

  最后把之前取到的路由中的组件通过render函数进行渲染。

  下面再来说一下<router-link>的实现,还是先来看一下render函数:

render (h: Function) {
    const router = this.$router
    const current = this.$route
    const { location, route, href } = router.resolve(
      this.to,
      current,
      this.append
    )

    const classes = {}
    const globalActiveClass = router.options.linkActiveClass
    const globalExactActiveClass = router.options.linkExactActiveClass

    const activeClassFallback =
      globalActiveClass == null ? 'router-link-active' : globalActiveClass
    const exactActiveClassFallback =
      globalExactActiveClass == null
        ? 'router-link-exact-active'
        : globalExactActiveClass
    const activeClass =
      this.activeClass == null ? activeClassFallback : this.activeClass
    const exactActiveClass =
      this.exactActiveClass == null
        ? exactActiveClassFallback
        : this.exactActiveClass

    const compareTarget = route.redirectedFrom
      ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
      : route

    classes[exactActiveClass] = isSameRoute(current, compareTarget)
    classes[activeClass] = this.exact
      ? classes[exactActiveClass]
      : isIncludedRoute(current, compareTarget)

    const handler = e => {
      if (guardEvent(e)) {
        if (this.replace) {
          router.replace(location, noop)
        } else {
          router.push(location, noop)
        }
      }
    }

    const on = { click: guardEvent }
    if (Array.isArray(this.event)) {
      this.event.forEach(e => {
        on[e] = handler
      })
    } else {
      on[this.event] = handler
    }

    if (this.tag === 'a') {
      data.on = on
      data.attrs = { href }
    } else {
      const a = findAnchor(this.$slots.default)
      if (a) {
        a.isStatic = false
        const aData = (a.data = extend({}, a.data))
        aData.on = aData.on || {}
        for (const event in aData.on) {
          const handler = aData.on[event]
          if (event in on) {
            aData.on[event] = Array.isArray(handler) ? handler : [handler]
          }
        }
        for (const event in on) {
          if (event in aData.on) {
            aData.on[event].push(on[event])
          } else {
            aData.on[event] = handler
          }
        }
        const aAttrs = (a.data.attrs = extend({}, a.data.attrs))
        aAttrs.href = href
      } else {
        data.on = on
      }
    }

    return h(this.tag, data, this.$slots.default)
  }

  通过router.resolve来做路由解析,生成要跳转的路由。之后对exactActiveClass和activeClass做处理。之后监听点击事件或者其他可以通过prop传入的事件类型,执行hander函数,最终执行router.push或者router.replace函数。

replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        this.history.replace(location, resolve, reject)
      })
    } else {
      this.history.replace(location, onComplete, onAbort)
    }
  }

  最后再把<router-link>渲染成标签。

  那么问题来了,当我们执行transitionTo来更改路由线路后,组件是如何重新渲染的呢?在beforeCreate钩子中,有这样的一段逻辑:

Vue.util.defineReactive(this, '_route', this._router.history.current)

  由于我们把根 Vue 实例的_route 属性定义成响应式的,我们在每个 <router-view> 执行 render 函数的时候,都会访问 parent.$route,如我们之前分析会访问 this._routerRoot._route,触发了它的 getter,相当于<router-view> 在执行render函数渲染的时候对它有依赖,然后在<router-link>执行完 transitionTo 后,修改 app._route 的时候,又触发了setter,因此会通知 <router-view> 的渲染 watcher 更新,重新渲染组件。

最后推荐一个干货满满的公众号: