We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
在上篇中, 大致分析了 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 源码分析-整体流程
The text was updated successfully, but these errors were encountered:
有两个疑惑,像以下这些赋值给 data 的属性是给谁用的?
data.routerViewDepth = depth data.hook = {}
还有 parent 自带的一些属性是 vue 里自带的吗?
parent._routerViewCache parent._inactive
Sorry, something went wrong.
No branches or pull requests
在上篇中, 大致分析了
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 源码分析-整体流程
The text was updated successfully, but these errors were encountered: