简述
hashchange
-->
match route-->
set vm._route-->
render matched component 监听hashchange⽅法 window.addEventListener('hashchange', () => { // this.transitionTo(...)}) 进⾏地址匹配,得到对应当前地址的 route。 将其设置到对应的 vm._route 上。侵⼊vue监听_route变量⽽触发更新流程最后是router-view组件调⽤render函数渲染匹配到的route 测试代码 怎么注⼊进的 vue ⼀个 install 函数,把 $route 挂载到了 Vue.prototype 上,保证 Vue 的所有组件实例,都是取同⼀份 router。并且在⾥⾯注册了 RouterView 和 RouterLink 组件 function install(Vue) { // ... Vue.mixin({ beforeCreate: function beforeCreate() { // ... this._routerRoot = this; this._router = this.$options.router; this._router.init(this); Vue.util.defineReactive(this, '_route', this._router.history.current); // ... }, destroyed: function destroyed() { // ... } }); Object.defineProperty(Vue.prototype, '$router', { get: function get() { return this._routerRoot._router } }); Object.defineProperty(Vue.prototype, '$route', { get: function get() { return this._routerRoot._route } }); Vue.component('RouterView', View); Vue.component('RouterLink', Link); // ...} VueRouter.install = install; 最后进⼊了 vue 的初始化逻辑⾥ initUse 函数⾥去触发插件的 install 函数执⾏。 router 是个什么结构 详见 function VueRouter (options),下⾯代码中需要注意三点: app 将会挂上 vue 实例对象 mode 代表⽤户配置的路由模式,默认是 hash,也就是使⽤ url 上的 hash 部分作为路由路径的判定。history 将会挂载上⽤户曾经的访问的记录数组。 var VueRouter = function VueRouter (options) { this.app = null; this.apps = []; this.options = options; this.beforeHooks = []; this.resolveHooks = []; this.afterHooks = []; this.matcher = createMatcher(options.routes || [], this); var mode = options.mode || 'hash'; // ... this.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: { assert(false, (\"invalid mode: \" + mode)); } }}; RouterView 组件长什么样 看下⽂代码,总结⼀下关键的步骤: 最关键的⼀步 var component = cache[name] = matched.components[name]; 获取到具体是那个组件,这⾥的 component 其实是 { template: \" __proto__: Object} 然后最后⾯就是调⽤ h(component, data, children) 完成渲染,h其实是 Vue 实例的 $createElement 函数,它会具体解析此 template 成为视图渲染。 var View = { name: 'RouterView', functional: true, props: { name: { type: String, default: 'default' } }, render: function render (_, ref) { var props = ref.props; var children = ref.children; var parent = ref.parent; var data = ref.data; // used by devtools to display a router-view badge data.routerView = true; // directly use parent context's createElement() function // so that components rendered by router-view can resolve named slots var h = parent.$createElement; var name = props.name; var route = parent.$route; var cache = parent._routerViewCache || (parent._routerViewCache = {}); // ... var component = cache[name] = matched.components[name]; // ... return h(component, data, children) } }; RouterLink 呢? 很精妙,此组件的 props 默认把 tag 设置为 a,并且代码中还⽀持 slotScope 插槽。 最后⼀样 h(this.tag, data, this.$slots.default) 去渲染,所以此组件渲染后的标签才会默认是 a 标签呀。。 var Link = { name: 'RouterLink', props: { to: { type: toTypes, required: true }, tag: { type: String, default: 'a' }, exact: Boolean, append: Boolean, replace: Boolean, activeClass: String, exactActiveClass: String, event: { type: eventTypes, default: 'click' } }, render: function render(h) { var router = this.$router; var current = this.$route; var ref = router.resolve( this.to, current, this.append ); // ... var href = ref.href; // ... var data = { class: classes }; var scopedSlot = !this.$scopedSlots.$hasNormal && this.$scopedSlots.default // ... if (scopedSlot) { if (scopedSlot.length === 1) { return scopedSlot[0] } else if (scopedSlot.length > 1 || !scopedSlot.length) { // ... return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot) } } if (this.tag === 'a') { data.on = on; data.attrs = { href: href }; } else { // ... } return h(this.tag, data, this.$slots.default) }}; 路由控制是怎么做的 本质上就是改变了 hash hashchange 的事件监听触发,接着去触发 HashHistory 实例⾥的 updateRoute 函数,updateRoute 函数⾥触发回调去更新 route 对象,route 对象更新就⾛⼊了 vue ⾃⾝的 set触发⼴播通知被观察者了。 VueRouter.prototype.back = function back () { this.go(-1); }; VueRouter.prototype.go = function go (n) { this.history.go(n); }; HashHistory.prototype.go = function go (n) { window.history.go(n);};// ... window.addEventListener( supportsPushState ? 'popstate' : 'hashchange', function () { var current = this$1.current; // ... this$1.transitionTo(getHash(), function (route) { if (supportsScroll) { handleScroll(this$1.router, route, current, true); } if (!supportsPushState) { replaceHash(route.fullPath); } }); });// ... History.prototype.transitionTo = function transitionTo( location, onComplete, onAbort) { var this$1 = this; var route = this.router.match(location, this.current); this.confirmTransition( route, function () { this$1.updateRoute(route); // ... }, function (err) { // ... } );};// ... History.prototype.updateRoute = function updateRoute(route) { var prev = this.current; this.current = route; // 这⾥的 cb 就是下⾯⼀段的 history.listen this.cb && this.cb(route); this.router.afterHooks.forEach(function (hook) { hook && hook(route, prev); });};// ... history.listen(function (route) { this$1.apps.forEach(function (app) { // 改变 app._route 就会进⼊ vue 实例⾃⾝的 get/set 中,然后⾃⼰触发更新。 // 因为上⽂ install 函数⾥做了属性劫持 Vue.util.defineReactive(this, '_route', this._router.history.current); app._route = route; });}); 钩⼦是怎么做的 this.beforeHooks 是个数组,registerHook 函数做的就只是往前⾯的数组⾥添加进⼊这个⽅法。 VueRouter.prototype.beforeEach = function beforeEach(fn) { return registerHook(this.beforeHooks, fn)}; VueRouter.prototype.beforeResolve = function beforeResolve(fn) { return registerHook(this.resolveHooks, fn)}; VueRouter.prototype.afterEach = function afterEach(fn) { return registerHook(this.afterHooks, fn)}; beforeHooks 在每次触发更新前的队列⾥调⽤ resolveHooks 执⾏是在下⽂的 runQueue ⾥,也就是是在触发更新前,但⽐ beforeHooks 晚,主要⽤于异步组件 afterHooks 的触发,是在 updateRoute 函数后,也就是开始触发 vue 的更新逻辑时,但并不⼀定视图已经更新完毕,因为 vue ⾃⾝也有不少的队列操作,不会⽴即更新。 // beforeHooks var queue = [].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(function (m) { return m.beforeEnter; }), // async components resolveAsyncComponents(activated)); runQueue(queue, iterator, function () { // ...} // resolveHooks runQueue(queue, iterator, function () { // ... // wait until async components are resolved before // extracting in-component enter guards var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid); var queue = enterGuards.concat(this$1.router.resolveHooks); runQueue(queue, iterator, function () { //... onComplete(route); //... });}); // afterHooks History.prototype.updateRoute = function updateRoute(route) { var prev = this.current; this.current = route; this.cb && this.cb(route); this.router.afterHooks.forEach(function (hook) { hook && hook(route, prev); });}; history 是怎么做的 hash 模式的路由是采⽤的 hash change 函数来做监听,并且操作浏览器 hash 做标识, ⽽ history 模式采⽤的 popstate event 来记住路由的状态,⽽ window.history.state ⾥的 key 只是⽤时间来⽣成的⼀个缓存。 HTML5History.prototype.push = function push (location, onComplete, onAbort) { var this$1 = this; var ref = this; var fromRoute = ref.current; this.transitionTo(location, function (route) { pushState(cleanPath(this$1.base + route.fullPath)); handleScroll(this$1.router, route, fromRoute, false); onComplete && onComplete(route); }, onAbort);}; function pushState (url, replace) { saveScrollPosition(); // try...catch the pushState call to get around Safari // DOM Exception 18 where it limits to 100 pushState calls var history = window.history; try { if (replace) { history.replaceState({ key: getStateKey() }, '', url); } else { history.pushState({ key: setStateKey(genStateKey()) }, '', url); } } catch (e) { window.location[replace ? 'replace' : 'assign'](url); }} function genStateKey () { return Time.now().toFixed(3)} 因篇幅问题不能全部显示,请点此查看更多更全内容Hello App!
Copyright © 2019- sarr.cn 版权所有 赣ICP备2024042794号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务