Open dwqs opened 7 years ago
在上篇中, 大致分析了 vue-router 的整体流程. VueRouter 除了提供 router 功能, 还提供了两个组件: router-link 和 router-view, 源码均在 src/components 目录下.
vue-router
router
router-link
router-view
src/components
本文分析的 vue-router 的版本为 2.6.0.
<router-link> 组件通过 to 属性指定目标地址, 为用户提供路由导航. 默认渲染成带有正确链接的 <a> 标签. 其源码在 src/components/link.js:
<router-link>
to
<a>
src/components/link.js
/* @flow */ import {createRoute, isSameRoute, isIncludedRoute} from '../util/route' import {_Vue} from '../install' // 类型定义 const toTypes: Array<Function> = [String, Object] const eventTypes: Array<Function> = [String, Array] export default { // 组件名 name: 'router-link', props: { // 目标路由 to: { type: toTypes, required: true }, // 目标标签 tag: { type: String, default: 'a' }, // 完整模式, 如果为 true 那么也就意味着 // 绝对相等的路由才会增加 activeClass // 否则是包含关系, 默认是 false exact: Boolean, // 是否在当前路径路径添加基路径 默认 false append: Boolean, // 是否使用 router.replace() 来替换 router.push() 默认 false replace: Boolean, // 链接激活时使用的 CSS 类名 activeClass: String, // 完整模式下链接激活时使用的 CSS 类名 exactActiveClass: String, // 触发导航的事件 event: { type: eventTypes, default: 'click' } }, render (h: Function) { // 得到 router 实例以及当前激活的 route 对象 const router = this.$router const current = this.$route // 获取当前匹配的 route信息 const {location, route, href} = router.resolve(this.to, current, this.append) const classes = {} const globalActiveClass = router.options.linkActiveClass const globalExactActiveClass = router.options.linkExactActiveClass // 获取 active class 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 = location.path ? createRoute(null, location, 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) } else { router.push(location) } } } // 事件监听 const on = {click: guardEvent} if (Array.isArray(this.event)) { this.event.forEach(e => { on[e] = handler }) } else { on[this.event] = handler } // 创建元素需要附加的数据 const data: any = { class: classes } if (this.tag === 'a') { data.on = on data.attrs = {href} } else { // 找到第一个 <a> 并绑定事件和 href 属性 const a = findAnchor(this.$slots.default) if (a) { // in case the <a> is a static node a.isStatic = false // 用于属性扩展 const extend = _Vue.util.extend const aData = a.data = extend({}, a.data) aData.on = on 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-link 的 event 绑定 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 // 忽略 `target="_blank" 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 } 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 } } } }
从上述代码可以看出, router-link 组件会根据绑定的事件类型和 to 属性, 去调用 push 或者 replace 更新路由, 同时根据 exact 属性来添加 active class.
push
replace
exact
active class
router-view 组件用于渲染与路由匹配的 components, 其源码在 src/components/view.js 中定义的:
components
src/components/view.js
import {warn} from '../util/warn' export default { // 组件名 name: 'router-view', // 显示指定为该组件是函数式组件 // 函数式组件: https://cn.vuejs.org/v2/guide/render-function.html#函数化组件 functional: true, props: { // 视图名称, 默认是 default name: { type: String, default: 'default' } }, render (_, {props, children, parent, data}) { data.routerView = true // 渲染函数 const h = parent.$createElement const name = props.name // route 对象 const route = parent.$route // 缓存 const cache = parent._routerViewCache || (parent._routerViewCache = {}) // 组件所在深度 let depth = 0 let inactive = false // 当 _routerRoot 指向 Vue 实例时就终止循环 while (parent && parent._routerRoot !== parent) { if (parent.$vnode && parent.$vnode.data.routerView) { depth++ } // 处理 keep-alive 组件 if (parent._inactive) { inactive = true } parent = parent.$parent } data.routerViewDepth = depth // 渲染缓存的 keep-alive 组件 if (inactive) { return h(cache[name], data, children) } // 根据组件深度获取对应的 route const matched = route.matched[depth] if (!matched) { // 没有对应的 route 就渲染一个空节点 cache[name] = null return h() } // 得到要渲染组件 const component = cache[name] = matched.components[name] // 添加注册钩子, 钩子会被注入到组件的生命周期钩子中 // 在 src/install.js, 会在 beforeCreate 钩子中调用 data.registerRouteInstance = (vm, val) => { // val 为空就注销注册 const current = matched.instances[name] if ( (val && current !== vm) || (!val && current === vm) ) { matched.instances[name] = val } } // 在 prepatch 钩子中注册组件实例, // 因为不同路由可能使用同一个组件, 便于组件复用 ; (data.hook || (data.hook = {})).prepatch = (_, vnode) => { matched.instances[name] = vnode.componentInstance } // resolve props data.props = resolveProps(route, matched.props && matched.props[name]) // 调用 createElement 函数 渲染匹配的组件 return h(component, data, children) } } 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.` ) } } }
router-view 被定义为一个无状态组件, 因为 router-view 只是一个函数, 用于渲染与路由对应的组件, 不需要管理或者监听任何传递给它的状态, 也没有生命周期方法, 所以渲染开销会低很多.
vue-router 源码分析-整体流程
有两个疑惑,像以下这些赋值给 data 的属性是给谁用的?
data.routerViewDepth = depth data.hook = {}
还有 parent 自带的一些属性是 vue 里自带的吗?
parent._routerViewCache parent._inactive
在上篇中, 大致分析了
vue-router
的整体流程. VueRouter 除了提供router
功能, 还提供了两个组件:router-link
和router-view
, 源码均在src/components
目录下.Link
<router-link>
组件通过to
属性指定目标地址, 为用户提供路由导航. 默认渲染成带有正确链接的<a>
标签. 其源码在src/components/link.js
:从上述代码可以看出,
router-link
组件会根据绑定的事件类型和to
属性, 去调用push
或者replace
更新路由, 同时根据exact
属性来添加active class
.View 组件
router-view
组件用于渲染与路由匹配的components
, 其源码在src/components/view.js
中定义的:router-view
被定义为一个无状态组件, 因为router-view
只是一个函数, 用于渲染与路由对应的组件, 不需要管理或者监听任何传递给它的状态, 也没有生命周期方法, 所以渲染开销会低很多.相关文章
vue-router 源码分析-整体流程