开头
最后来了解了解 vue-router 的两个组件 <router-link>
和 <router-view>
。
这两个组件的使用方式如下:
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
复制代码
在打开源码之前,先来简单思考一下,组件可以怎样去实现。
<router-link>
可以是一个 <a>
标签,to 是它的 props,然后绑定一个点击事件。
还记得 编程式的导航 吗?在之前的源码中,我们也看到 this.history 实现了 push、replace 等方法,通过点击触发这些方法即可触发路由的改变。
// 伪代码
Vue.component('router-link', {
props: {
to: String
},
template: '<a v-on="handleClick"><slot></slot></a>',
methods: {
handleClick () {
this.$router.push(this.to)
}
}
})
复制代码
路由的改变,会修改到 this.$route
,利用 Vue 的响应式,在 <router-view>
里使用到 this.$route
,便能够及时的获取到相应的组件并渲染出来。
// 伪代码
Vue.component('router-view', {
render (h, { parent }) {
const route = parent.$route
// route 里不一定有 component
// 表示通过 route 可以找到 component 的意思
const component = route.component
return h(component);
}
})
复制代码
这只是最简单的猜想,实现实现的代码远比上面的要复杂得多。下面就来真正的看看它们的具体实现吧。
router-link
router-link
组件的写法是 渲染函数 的写法。
主要实现了几块功能:
- 定义正确的跳转 url。
- 定义切换的高亮样式。
- 定义点击事件。
- 设置 data 对象。
router-link
的大体框架是这样的:
export default {
name: 'router-link',
props: {
to: {
type: toTypes,
required: true
},
tag: {
type: String,
default: 'a'
}
// ...
},
render (h: Function) {
// ...
return h(this.tag, data, this.$slots.default)
}
}
复制代码
render 里就是实现的几块功能。
定义正确的跳转 url
const router = this.$router
const current = this.$route
const to = normalizeLocation(this.to, current, this.append)
const resolved = router.match(to)
const fullPath = resolved.redirectedFrom || resolved.fullPath
const base = router.history.base
const href = base ? cleanPath(base + fullPath) : fullPath
复制代码
最后得到 href 作为 <a>
标签的跳转属性,具体细节不展开了。
定义切换的高亮样式
const classes = {}
const activeClass = this.activeClass || router.options.linkActiveClass || 'router-link-active'
// 省略一堆代码
classes[activeClass] = true // or false
复制代码
这里可以看到,高亮样式默认就是 'router-link-active'。
定义点击事件
const on = {
click: (e) => {
// ...
e.preventDefault()
if (this.replace) {
router.replace(to)
} else {
router.push(to)
}
}
}
复制代码
点击事件里其实就是调用触发 router 的 push 或 replace,跟编程式导航类似。
设置 data 对象
const data: any = {
class: classes
}
if (this.tag === 'a') {
data.on = on
data.attrs = { href }
} else {
// find the first <a> child and apply listener and href
const a = findAnchor(this.$slots.default)
if (a) {
const aData = a.data || (a.data = {})
aData.on = on
const aAttrs = aData.attrs || (aData.attrs = {})
aAttrs.href = href
} else {
// doesn't have <a> child, apply listener to self
data.on = on
}
}
复制代码
这里有一个判断,如果 tag 不是 <a>
标签的话,就会递归使用 findAnchor
函数去找 slots 里面的 <a>
标签。最后还是没有的话,就只能给 router-link 自己赋值 on 点击事件。
最后,将 createElement 函数返回,即完成 router-link
组件的实现。
return h(this.tag, data, this.$slots.default)
复制代码
router-view
router-view
是一个 函数式组件,专门只做渲染的工作。
最主要就是找到要显示的组件,期间会优先取已经缓存的组件,其次是取嵌套了的组件。
router-view
的大体框架是这样的:
export default {
name: 'router-view',
functional: true,
props: {
name: {
type: String,
default: 'default'
}
},
render (h, { props, children, parent, data }) {
// ...
return h(component, data, children)
}
}
复制代码
路由触发
其中使用到了 $route。
const route = parent.$route
// ...
const matched = route.matched[depth]
复制代码
回到 install,当时使用了 Vue 将 $route 进行了响应式处理:
Object.defineProperty(Vue.prototype, '$route', {
get () { return this.$root._route }
})
复制代码
然后还记得路由初始化时的监听:
this.history.listen(route => {
this.app._route = route
})
复制代码
一旦修改了 _route,即修改到了 $route,也会同时触发到 route-view
的 render,从而实现了路由的跳转(即路由的切换)。
嵌套路由处理
route-view
通过 depth 和 route.matched 来找到嵌套的路由,从而实现嵌套路由的渲染。
let depth = 0
while (parent) {
if (parent.$vnode && parent.$vnode.data.routerView) {
depth++
}
parent = parent.$parent
}
data.routerViewDepth = depth
const matched = route.matched[depth]
复制代码
keep-alive 处理
使用到了 keep-alive,那就会有 cache 缓存,那么组件就直接用缓存的即可。
const cache = parent._routerViewCache || (parent._routerViewCache = {})
let inactive = false
while (parent) {
// ...
if (parent._inactive) {
inactive = true
}
parent = parent.$parent
}
const matched = route.matched[depth]
const name = props.name
const component = inactive
? cache[name]
: (cache[name] = matched.components[name])
复制代码
最后,找到对应的要渲染的组件,将 createElement 函数返回,即完成 router-view
组件的实现。
return h(component, data, children)
复制代码
最后
以上只是简略得对 <router-link>
和 <route-view>
的解读,想更加深入了解代码实现,可以移动到 vue-router源码分析-整体流程。
至此,vue-router 的源码解读告一段落,鼓掌。