0.前情提要
在Vue.use(VueRouter)的时候注册的内置组件
function install(Vue){
// ...
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
//...
}
1. <router-link>
props
to: 可以是字符串,或者是描述目标路由的对象tag: 渲染的html标签名,默认是<a>activeClass: 样式相关exactActiveClass: 样式相关event: 触发该导航的事件,默认是click点击apped: 在当前路径下添加基路径 具体可查API 参考 | Vue Router (vuejs.org)
export default {
name: 'RouterLink',
props: {
to: {
type: toTypes,
required: true
},
tag: {
type: String,
default: 'a'
},
custom: Boolean,
exact: Boolean,
exactPath: Boolean,
append: Boolean,
replace: Boolean,
activeClass: String,
exactActiveClass: String,
ariaCurrentValue: {
type: String,
default: 'page'
},
event: {
type: eventTypes,
default: 'click'
}
},
render (h: Function) {
const router = this.$router
const current = this.$route
// 匹配到目标路由信息
const { location, route, href } = router.resolve(
this.to,
current,
this.append
)
//...
const ariaCurrentValue = classes[exactActiveClass] ? this.ariaCurrentValue : null
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)) {
// 循环props中的event事件名添加handler
this.event.forEach(e => {
on[e] = handler
})
} else {
on[this.event] = handler
}
// 开始构造data
// 先加样式类
const data: any = { class: classes }
// 暴露作用域插槽
const scopedSlot =
!this.$scopedSlots.$hasNormal &&
this.$scopedSlots.default &&
this.$scopedSlots.default({
href,
route,
navigate: handler,
isActive: classes[activeClass],
isExactActive: classes[exactActiveClass]
})
if (scopedSlot) {
// 插槽只有一个子元素,直接渲染该子元素
if (scopedSlot.length === 1) {
return scopedSlot[0]
} else if (scopedSlot.length > 1 || !scopedSlot.length) {
// 多个子元素,就用span包裹一层
return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
}
}
// 标签是a可以用href来跳转
if (this.tag === 'a') {
data.on = on
data.attrs = { href, 'aria-current': ariaCurrentValue }
} else {
// 在插槽中找到第一个a标签
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) {
// on[event] is always a function
aData.on[event].push(on[event])
} else {
aData.on[event] = handler
}
}
// 增加两个属性
const aAttrs = (a.data.attrs = extend({}, a.data.attrs))
aAttrs.href = href
aAttrs['aria-current'] = ariaCurrentValue
} else {
// 没有a标签那就把这些事件给加上
data.on = on
}
}
// 渲染
return h(this.tag, data, this.$slots.default)
}
}
// 阻止一些事件发生时的跳转
function guardEvent (e) {
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
if (e.defaultPrevented) return
if (e.button !== undefined && e.button !== 0) return
if (e.currentTarget && e.currentTarget.getAttribute) {
const target = e.currentTarget.getAttribute('target')
if (/\b_blank\b/i.test(target)) return
}
if (e.preventDefault) {
e.preventDefault()
}
return true
}
// 递归查找第一个a标签
function findAnchor (children) {
if (children) {
let child
for (let i = 0; i < children.length; i++) {
child = children[i]
if (child.tag === 'a') {
return child
}
if (child.children && (child = findAnchor(child.children))) {
return child
}
}
}
}
2. <router-view>
- 该组件是函数式组件,不会创建实例,所以他的
h方法也得从parent去拿 props只有一个name,这是为了做命名视图的功能的
export default {
name: 'RouterView',
functional: true,
props: {
name: {
type: String,
default: 'default'
}
},
render (_, { props, children, parent, data }) {
data.routerView = true
const h = parent.$createElement
const name = props.name
const route = parent.$route
const cache = parent._routerViewCache || (parent._routerViewCache = {})
// 组件嵌套的层次,为了后面知道需要渲染的是路由表中的哪个路由状态
let depth = 0
// 是否在keep-alive中
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
if (inactive) {
const cachedData = cache[name]
const cachedComponent = cachedData && cachedData.component
if (cachedComponent) {
if (cachedData.configProps) {
fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps)
}
return h(cachedComponent, data, children)
} else {
return h()
}
}
// 通过matched和depth获取到当前组件应该渲染的路由对象
const matched = route.matched[depth]
// 获取到需要渲染的组件名
const component = matched && matched.components[name]
// 有可能到这一层已经没有匹配的东西了
// 或者匹配到了但是没有对应的组件渲染
if (!matched || !component) {
cache[name] = null
return h()
}
// 缓存组件
cache[name] = { component }
data.registerRouteInstance = (vm, val) => {
const current = matched.instances[name]
if (
(val && current !== vm) ||
(!val && current === vm)
) {
matched.instances[name] = val
}
}
;(data.hook || (data.hook = {})).prepatch = (_, vnode) => {
matched.instances[name] = vnode.componentInstance
}
// register instance in init hook
// in case kept-alive component be actived when routes changed
data.hook.init = (vnode) => {
if (vnode.data.keepAlive &&
vnode.componentInstance &&
vnode.componentInstance !== matched.instances[name]
) {
matched.instances[name] = vnode.componentInstance
}
handleRouteEntered(route)
}
const configProps = matched.props && matched.props[name]
if (configProps) {
extend(cache[name], {
route,
configProps
})
fillPropsinData(component, data, route, configProps)
}
return h(component, data, children)
}
}
function fillPropsinData (component, data, route, configProps) {
// resolve props
let propsToPass = data.props = resolveProps(route, configProps)
if (propsToPass) {
// clone to prevent mutation
propsToPass = data.props = extend({}, propsToPass)
// pass non-declared props as attrs
const attrs = data.attrs = data.attrs || {}
for (const key in propsToPass) {
if (!component.props || !(key in component.props)) {
attrs[key] = propsToPass[key]
delete propsToPass[key]
}
}
}
}
function resolveProps (route, config) {
switch (typeof config) {
case 'undefined':
return
case 'object':
return config
case 'function':
return config(route)
case 'boolean':
return config ? route.params : undefined
default:
if (process.env.NODE_ENV !== 'production') {
warn(
false,
`props in "${route.path}" is a ${typeof config}, ` +
`expecting an object, function or boolean.`
)
}
}
}