上一篇我们分析了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 更新,重新渲染组件。
最后推荐一个干货满满的公众号: