vue router 源码概览案例分析(2)

// src/util/route.js const route: Route = { name: location.name || (record && record.name), meta: (record && record.meta) || {}, path: location.path || 'https://www.jb51.net/', hash: location.hash || '', query, params: location.params || {}, fullPath: getFullPath(location, stringifyQuery), matched: record ? formatMatch(record) : [] }

src/history/base.js 源码文件中的 transitionTo() 是路由切换的核心方法

transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) { const route = this.router.match(location, this.current) this.confirmTransition(route, () => { ... }

路由实例的 push 和 replace 等路由切换方法,都是基于此方法实现路由切换的,例如 hash 模式的 push 方法:

// src/history/hash.js push (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this // 利用了 transitionTo 方法 this.transitionTo(location, route => { pushHash(route.fullPath) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort) }

transitionTo 方法内部通过一种异步函数队列化执⾏的模式来更新切换路由,通过 next 函数执行异步回调,并在异步回调方法中执行相应的钩子函数(即 导航守卫) beforeEach 、 beforeRouteUpdate 、 beforeRouteEnter 、 beforeRouteLeave

通过 queue 这个数组保存相应的路由参数:

// src/history/base.js const queue: Array<?NavigationGuard> = [].concat( // in-component leave guards extractLeaveGuards(deactivated), // global before hooks this.router.beforeHooks, // in-component update hooks extractUpdateHooks(updated), // in-config enter guards activated.map(m => m.beforeEnter), // async components resolveAsyncComponents(activated) )

通过 runQueue 以一种递归回调的方式来启动异步函数队列化的执⾏:

// src/history/base.js // 异步回调函数 runQueue(queue, iterator, () => { const postEnterCbs = [] const isValid = () => this.current === route // wait until async components are resolved before // extracting in-component enter guards const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid) const queue = enterGuards.concat(this.router.resolveHooks) // 递归执行 runQueue(queue, iterator, () => { if (this.pending !== route) { return abort() } this.pending = null onComplete(route) if (this.router.app) { this.router.app.$nextTick(() => { postEnterCbs.forEach(cb => { cb() }) }) } }) })

通过 next 进行导航守卫的回调迭代,所以如果在代码中显式声明了导航钩子函数,那么就必须在最后调用 next() ,否则回调不执行,导航将无法继续

// src/history/base.js const iterator = (hook: NavigationGuard, next) => { ... hook(route, current, (to: any) => { ... } else { // confirm transition and pass on the value next(to) } }) ... }

路由同步

在路由切换的时候, vue-router 会调用 push 、 go 等方法实现视图与地址 url 的同步

地址栏 url 与视图的同步

当进行点击页面上按钮等操作进行路由切换时, vue-router 会通过改变 window.location.href 来保持视图与 url 的同步,例如 hash 模式的路由切换:

// src/history/hash.js function pushHash (path) { if (supportsPushState) { pushState(getUrl(path)) } else { window.location.hash = path } }

上述代码,先检测当前浏览器是否支持 html5 的 History API ,如果支持则调用此 API 进行 href 的修改,否则直接对 window.location.hash 进行赋值 history 的原理与此相同,也是利用了 History API

视图与地址栏 url 的同步

当点击浏览器的前进后退按钮时,同样可以实现视图的同步,这是因为在路由初始化的时候,设置了对浏览器前进后退的事件监听器

下述是 hash 模式的事件监听:

// src/history/hash.js setupListeners () { ... window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', () => { const current = this.current if (!ensureSlash()) { return } this.transitionTo(getHash(), route => { if (supportsScroll) { handleScroll(this.router, route, current, true) } if (!supportsPushState) { replaceHash(route.fullPath) } }) }) }

history 模式与此类似:

// src/history/html5.js window.addEventListener('popstate', e => { const current = this.current // Avoiding first `popstate` event dispatched in some browsers but first // history route not updated since async guard at the same time. const location = getLocation(this.base) if (this.current === START && location === initLocation) { return } this.transitionTo(location, route => { if (supportsScroll) { handleScroll(router, route, current, true) } }) })

无论是 hash 还是 history ,都是通过监听事件最后来调用 transitionTo 这个方法,从而实现路由与视图的统一

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/844fe7505bb0859b422083ffe7702598.html