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
1、vue-router 是怎么被初始化以及装载的 2、路由改变是怎么更新视图的
vue-router
结合流程图来分析下面代码的执行流程
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const Home = { template: '<div>home</div>' } const Foo = { template: '<div>foo</div>' } const router = new VueRouter({ mode: 'history', routes: [ { path: '/', component: Home }, { path: '/foo', component: Foo }, ] }) new Vue({ router, template: ` <div id="app"> <router-view></router-view> </div> ` }).$mount('#app')
vue-router 作为 Vue 的一个插件,通过全局方法 Vue.use() 的方式将 vue-router 挂载到 Vue 上。
Vue
Vue.use()
对照流程图:
(1)、Vue.use(VueRouter) 首先会调用 install 方法, (2)、利用 mixin 往 Vue 混入一个 beforeCreat 钩子,接着 beforeCreat 钩子做了哪些事情呢, (3)、检测 Vue 的 options 有没有 router 实例,有的话会通过 Object.defineProperty 将 $router 和 $route 变成响应式对象, (4)、并挂载到 Vue 的 prototype 上, (5)、最后会往 Vue 的 $options.components 注册两个全局组件 router-view 和 router-link
Vue.use(VueRouter)
install
mixin
beforeCreat
options
router
Object.defineProperty
$router
$route
prototype
$options.components
router-view
router-link
vue-router 挂载完,new VueRouter 实例化 VueRouter 有做了哪些事情呢。
new VueRouter
VueRouter
(1)、刚开始当然执行构造函数做一些初始化操作(流程图没体现出来) (2)、接着会执行 createMatcher(options, routers) 根据 VueRouter 传入的 routes 数组创建 path 和 component 的匹配规则 (3)、根据 VueRouter 传入的 mode 实例化 history 属性 (4)、定义一些钩子函数 beforeEach beforeResolve afterEach (流程图没体现出来) (5)、定义一些路由方法 push、replace、go、back (6)、定义 init() 方法,该方法是在Vue组件的 beforeCreat 钩子下会被调用,执行的时候会去匹配对应的 path 并找到对应的 component 再渲染到页面上(先有个印象就好)
createMatcher(options, routers)
routes
path
component
mode
history
beforeEach
beforeResolve
afterEach
push
replace
go
back
init()
前面做了这么一大波操作,那路由改变是怎么触发组件更新的呢,或者一个Vue项目初始化的时候是怎么加载路由组件的呢,具体流程是怎么样的。 如果你还没被流程图绕晕的话,再看一眼流程图的 黑色 线路。
黑色
前面我们提到了 Vue.use() 时会利用 mixin 往 Vue 混入一个 beforeCreat 钩子,注意这个钩子是全局钩子,意味着每个 Vue 组件都会调用这个钩子,包括根组件自身。
跟着黑色 线路走:
(1)、在根组件实例化后就会调用 beforeCreat 钩子, (2)、接着会调用 route 实例的 init() 方法,init() 方法会执行 transitionTo 来改变对应的路由。 (3)、那对应的路由从哪里找呢,就是通过 match() 方法,去匹配我们上面有提到的 createMatcher 方法已经做好的 path 和 component 的匹配规则。 (4)、通过 path 找到对应的 component 后会再执行 confirmTransition 来确认改变路由, (5)、接着 updateRoute 跟更新路由,最后将匹配的 component 给 current, (6)、执行这些操作后其实是有个回调函数的,回调函数会跟新 _route 属性,该属性也是一个响应式对象, (6)、_route 更新就会触发视图渲染,也就是调用 app 的 render() 函数,接着也触发了 router-view 组件的 render , (7)、router-view 组件的 render 会获取匹配的 component 渲染到视图上。
route
transitionTo
match()
createMatcher
confirmTransition
updateRoute
current,
_route
app
render()
render
通过流程图大致分析了 vue-router 的初始化和装载过程,以及路由跟新的流程。了解完大概思路下面进行源码层的分析就不会那么摸不着头绪了。
故事的开端还是需要从路由的注册说起。
vue-router 提供了一个 install 方法, Vue.use(VueRouter) 的时候会执行该方法。具体可看 vue 插件机制
import { install } from './install' export default class VueRouter { static install: () => void; static version: string; // ... } VueRouter.install = install VueRouter.version = '__VERSION__' if (inBrowser && window.Vue) { window.Vue.use(VueRouter) }
install 方法里头做了这些事情:
import View from './components/view' import Link from './components/link' export let _Vue export function install (Vue) { // 确保 install 调用一次 if (install.installed && _Vue === Vue) return install.installed = true // 把 Vue 赋值给全局变量给 VueRouter 用 _Vue = Vue const isDef = v => v !== undefined const registerInstance = (vm, callVal) => { let i = vm.$options._parentVnode if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal) } } Vue.mixin({ // 给每个组件混入 beforeCreate 钩子,注意,这意味着每个组件实例化时都会执行调用这个钩子 beforeCreate () { // 判断是不是一个路由应用,有没有传入 router options if (isDef(this.$options.router)) { // 根组件会执行 this._routerRoot = this this._router = this.$options.router // 初始化路由 this._router.init(this) // 将 _route 属性实现双向绑定,改变时会触发组件渲染 Vue.util.defineReactive(this, '_route', this._router.history.current) } else { // 其他子组件会执行 this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } registerInstance(this, this) }, destroyed () { registerInstance(this) } }) // 能够在组件上使用 this.$router 获取 _router 实例 Object.defineProperty(Vue.prototype, '$router', { get () { return this._routerRoot._router } }) // 能够在组件上使用 this.$route 获取 _router 实例 Object.defineProperty(Vue.prototype, '$route', { get () { return this._routerRoot._route } }) // 全局注册组件 router-link 和 router-view Vue.component('RouterView', View) Vue.component('RouterLink', Link) }
上面有提到 Vue 根实例会挂载 _router 属性,及 $options.router 实例,该实例就是 VueRouter 实例
_router
$options.router
this._router = this.$options.router
const router = new VueRouter({ mode: 'history', routes: [ { path: '/', component: Home }, { path: '/foo', component: Foo }, ] })
接下来分析 VueRouter 实例化做了哪些操作
export default class VueRouter { constructor (options: RouterOptions = {}) { //... // 创建路由匹配对象 this.matcher = createMatcher(options.routes || [], this) let mode = options.mode || 'hash' this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false if (this.fallback) { mode = 'hash' } if (!inBrowser) { mode = 'abstract' } this.mode = mode // 根据 mode 采取不同的路由方式 switch (mode) { case 'history': this.history = new HTML5History(this, options.base) break case 'hash': this.history = new HashHistory(this, options.base, this.fallback) break case 'abstract': this.history = new AbstractHistory(this, options.base) break default: if (process.env.NODE_ENV !== 'production') { assert(false, `invalid mode: ${mode}`) } } } // ... }
(1)、 createMatcher
初始化部分主要是 createMatcher 的功能, createMatcher 会创建路由匹配对象,及 path 和 component 对应
export function createMatcher (routes: Array<RouteConfig>,router: VueRouter): Matcher { const { pathList, pathMap, nameMap } = createRouteMap(routes) console.log('==pathMap==', pathMap) function addRoutes (routes) { createRouteMap(routes, pathList, pathMap, nameMap) } function match (raw: RawLocation,currentRoute?: Route,redirectedFrom?: Location ): Route { const location = normalizeLocation(raw, currentRoute, false, router) //... return _createRoute(record, location, redirectedFrom) } // ... return { match, addRoutes } }
createMatcher 会返回一个 match 和 addRoutes 方法, 调用 match 方法能根据 path 返回对应的组件。这里隐藏了一些细节的分析,直接 log pathMap 在控制台查看 createRouteMap 创建匹配的结果就好
match
addRoutes
log pathMap
createRouteMap
可看到有这么一个结构
{ '': { path: '', components: {} }, '/foo': { path: '/foo', components: {} } }
调用 match 方法能拿到对应的匹配内容了。
上面分析了 VueRouter 的实例化,那路由的初始化在哪里执行的呢,之前有提到 beforeCreate 会执行 this._router.init(this) ,这里就做了路由的初始化操作。
beforeCreate
this._router.init(this)
Vue.mixin({ beforeCreate () { if (isDef(this.$options.router)) { // 判断是不是根组件 this._routerRoot = this this._router = this.$options.router this._router.init(this) // 初始化路由 Vue.util.defineReactive(this, '_route', this._router.history.current) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } registerInstance(this, this) }, destroyed () { registerInstance(this) } })
来看 init() 做了哪些事情
init (app: any /* Vue component instance */) { if (this.app) { return } this.app = app const history = this.history // 判断路由模式 if (history instanceof HTML5History) { // 路由跳转 history.transitionTo(history.getCurrentLocation()) } else if (history instanceof HashHistory) { // 添加 hashchange 监听 const setupHashListener = () => { history.setupListeners() } // 路由跳转 history.transitionTo( history.getCurrentLocation(), setupHashListener, setupHashListener ) } //注册监听事件, transitionTo 执行完会执行该回调,回调里头对组件的 _route 属性进行赋值,触发组件渲染 history.listen(route => { this.apps.forEach((app) => { app._route = route }) }) }
transitionTo 执行完会执行回调,回调里头去更新 _route 从而跟新视图,所以先再看下 transitionTo 做了什么。
transitionTo (location: RawLocation,onComplete?: Function,onAbort?: Function) { // 获取匹配的路由信息 const route = this.router.match(location, this.current) // 确认切换路由 this.confirmTransition( route, () => { this.updateRoute(route) onComplete && onComplete(route) this.ensureURL() // 执行回调 if (!this.ready) { this.ready = true this.readyCbs.forEach(cb => { cb(route) }) } }, err => { if (onAbort) { onAbort(err) } if (err && !this.ready) { this.ready = true this.readyErrorCbs.forEach(cb => { cb(err) }) } } ) }
transitionTo 主要是去获取匹配的路由信息,这个匹配路由信息上面已经分析过了。接着调用 confirmTransition ,执行完会调用一个回调 cb(route), 这个就会触发之前的监听事件:
cb(route)
history.listen(route => { this.apps.forEach((app) => { app._route = route // 更新 _route 从而触发组件更新 }) })
前面我们已经了解到了路由改变会经过一系列的操作,最终找到匹配的 component 从而跟新 route
app._route = route // 更新 _route 从而触发组件更新
而在讲解【1、路由注册】这一步我们有提到
Vue.util.defineReactive(this, '_route', this._router.history.current)
_route 是一个响应式属性,更新后会触发相应的视图更新,并且【1、路由注册】这一步我们还提到
// 全局注册组件 router-link 和 router-view Vue.component('RouterView', View) Vue.component('RouterLink', Link)
install 注册 vue-router 时顺带注册了一个全局组件 RouterView 。接下来我们来分析下 _route 属性的改变是如何触发视图更新的,也就是
RouterView
<router-view class="view"></router-view>
是如何渲染匹配的 component 的
router-view 源码
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 // 获取 _router const cache = parent._routerViewCache || (parent._routerViewCache = {}) 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 // ... //获取到匹配的 component const matched = route.matched[depth] const component = matched && matched.components[name] // ... // 调用 createElement 函数 渲染匹配的组件 return h(component, data, children) } }
router-view 是一个函数组件, _router 改变触发 render 执行,render 内部获取到匹配的 component 进行渲染。
最后 router-view 被渲染成相应的组件。
我们通过 vue-router 的挂载 -> 路由匹配 -> 组件渲染 的流程大致分析了 vue-router 的执行原理,我们隐藏和很多细节和功能没分析,比如 路由的 history 的 hash 模式、路由的具体匹配过程、路由守卫的功能等等。这些后续再具体分析,但了解了整体流程,接下来的细节功能就一步步拆开分析就清楚多了。
hash
最后再放一张最开始的流程图
The text was updated successfully, but these errors were encountered:
No branches or pull requests
vue-router 源码分析
思考 🤔
1、
vue-router
是怎么被初始化以及装载的2、路由改变是怎么更新视图的
流程图
结合流程图来分析下面代码的执行流程
1、vue-router 初始化以及装载
vue-router
作为Vue
的一个插件,通过全局方法Vue.use()
的方式将vue-router
挂载到 Vue 上。对照流程图:
(1)、
Vue.use(VueRouter)
首先会调用install
方法,(2)、利用
mixin
往Vue
混入一个beforeCreat
钩子,接着beforeCreat
钩子做了哪些事情呢,(3)、检测
Vue
的options
有没有router
实例,有的话会通过Object.defineProperty
将$router
和$route
变成响应式对象,(4)、并挂载到
Vue
的prototype
上,(5)、最后会往
Vue
的$options.components
注册两个全局组件router-view
和router-link
vue-router
挂载完,new VueRouter
实例化VueRouter
有做了哪些事情呢。对照流程图:
(1)、刚开始当然执行构造函数做一些初始化操作(流程图没体现出来)
(2)、接着会执行
createMatcher(options, routers)
根据VueRouter
传入的routes
数组创建path
和component
的匹配规则(3)、根据
VueRouter
传入的mode
实例化history
属性(4)、定义一些钩子函数
beforeEach
beforeResolve
afterEach
(流程图没体现出来)(5)、定义一些路由方法
push
、replace
、go
、back
(6)、定义
init()
方法,该方法是在Vue组件的beforeCreat
钩子下会被调用,执行的时候会去匹配对应的path
并找到对应的component
再渲染到页面上(先有个印象就好)2、路由改变是怎么触发匹配的组件更新的
前面做了这么一大波操作,那路由改变是怎么触发组件更新的呢,或者一个Vue项目初始化的时候是怎么加载路由组件的呢,具体流程是怎么样的。
如果你还没被流程图绕晕的话,再看一眼流程图的
黑色
线路。前面我们提到了
Vue.use()
时会利用mixin
往Vue
混入一个beforeCreat
钩子,注意这个钩子是全局钩子,意味着每个 Vue 组件都会调用这个钩子,包括根组件自身。跟着
黑色
线路走:(1)、在根组件实例化后就会调用
beforeCreat
钩子,(2)、接着会调用
route
实例的init()
方法,init()
方法会执行transitionTo
来改变对应的路由。(3)、那对应的路由从哪里找呢,就是通过
match()
方法,去匹配我们上面有提到的createMatcher
方法已经做好的path
和component
的匹配规则。(4)、通过
path
找到对应的component
后会再执行confirmTransition
来确认改变路由,(5)、接着
updateRoute
跟更新路由,最后将匹配的component
给current,
(6)、执行这些操作后其实是有个回调函数的,回调函数会跟新
_route
属性,该属性也是一个响应式对象,(6)、
_route
更新就会触发视图渲染,也就是调用app
的render()
函数,接着也触发了router-view
组件的render
,(7)、
router-view
组件的render
会获取匹配的component
渲染到视图上。源码解析
通过流程图大致分析了
vue-router
的初始化和装载过程,以及路由跟新的流程。了解完大概思路下面进行源码层的分析就不会那么摸不着头绪了。1、路由注册
故事的开端还是需要从路由的注册说起。
vue-router
提供了一个install
方法,Vue.use(VueRouter)
的时候会执行该方法。具体可看 vue 插件机制install 方法里头做了这些事情:
2、VueRouter 实例
上面有提到
Vue
根实例会挂载_router
属性,及$options.router
实例,该实例就是VueRouter
实例接下来分析
VueRouter
实例化做了哪些操作(1)、 createMatcher
初始化部分主要是
createMatcher
的功能,createMatcher
会创建路由匹配对象,及path
和component
对应createMatcher
会返回一个match
和addRoutes
方法, 调用match
方法能根据path
返回对应的组件。这里隐藏了一些细节的分析,直接log pathMap
在控制台查看createRouteMap
创建匹配的结果就好可看到有这么一个结构
调用
match
方法能拿到对应的匹配内容了。3、路由初始化
上面分析了
VueRouter
的实例化,那路由的初始化在哪里执行的呢,之前有提到beforeCreate
会执行this._router.init(this)
,这里就做了路由的初始化操作。来看
init()
做了哪些事情transitionTo
执行完会执行回调,回调里头去更新_route
从而跟新视图,所以先再看下transitionTo
做了什么。transitionTo
主要是去获取匹配的路由信息,这个匹配路由信息上面已经分析过了。接着调用confirmTransition
,执行完会调用一个回调cb(route)
,这个就会触发之前的监听事件:
4、vue-router
前面我们已经了解到了路由改变会经过一系列的操作,最终找到匹配的
component
从而跟新route
而在讲解【1、路由注册】这一步我们有提到
_route
是一个响应式属性,更新后会触发相应的视图更新,并且【1、路由注册】这一步我们还提到install
注册vue-router
时顺带注册了一个全局组件RouterView
。接下来我们来分析下_route
属性的改变是如何触发视图更新的,也就是是如何渲染匹配的
component
的router-view
是一个函数组件,_router
改变触发render
执行,render
内部获取到匹配的component
进行渲染。最后
router-view
被渲染成相应的组件。小结
我们通过
vue-router
的挂载 -> 路由匹配 -> 组件渲染 的流程大致分析了vue-router
的执行原理,我们隐藏和很多细节和功能没分析,比如路由的
history
的hash
模式、路由的具体匹配过程、路由守卫的功能等等。这些后续再具体分析,但了解了整体流程,接下来的细节功能就一步步拆开分析就清楚多了。最后再放一张最开始的流程图
The text was updated successfully, but these errors were encountered: