前言
路由最初是由后端提出,浏览器发出请求,服务器根据路由的配置,返回相应信息。后来随着ajax的流行,异步数据请求交互在浏览器局部刷新。单页面更是把这种方式用到了极致,不仅仅是在页面交互是无刷新的,连页面跳转都是无刷新的,本文就从源码来分析下 Vue路由vue-router的实现原理。
1.导入插件
import Router from 'vue-router' export default class VueRouter { static install: () => void; static version: string; ...} 复制代码
通过import导入的插件是一个VueRouter类。
2.路由注册
Vue.use(Router)
通过Vue.use方法对路由做一个注册,把导入的插件传进去,和Vue关联起来。这个方法会调用plugin来执行插件的install方法,所以一般写插件都会有一个install方法,install方法里,利用Vue.mixin方法给每一个组件注入钩子函数beforeCreate和destroyed前者获得路由实例,初始化路由配置。同时内置全局RouterView和RouterLink两个组件。
源码分析:export function install (Vue) {//判断install方法是否调用一次 if (install.installed && _Vue === Vue) return install.installed = true _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) } }//给全局组件注入beforeCreate和destoryed钩子函数,Vue实例创建时,会执行 Vue.mixin({ beforeCreate () { //判断有无router实例 if (isDef(this.$options.router)) { this._routerRoot = this this._router = this.$options.router //路由初始化,会调用transitionTo做路径切换, 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) } }) //在原型上创建$router(为一个vueRouter实例,可以调用.push,.go等方法)//$route(为当前路由跳转对象可以获取name、path、query等)两个属性,//方便全局调用 Object.defineProperty(Vue.prototype, '$router', { get () { return this._routerRoot._router } }) Object.defineProperty(Vue.prototype, '$route', { get () { return this._routerRoot._route } }) // 全局注册组件 router-link(路由跳转) 和 router-view(路由对应组件内容展示) Vue.component('RouterView', View) Vue.component('RouterLink', Link) const strats = Vue.config.optionMergeStrategies // use the same hook merging strategy for route hooks strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created}复制代码
3.vueRouter对象
const route = new Router({ routes: [ { path: '/helloword',redirect: '/welcome' , name: 'HelloWorld', component: HelloWorld, }, { path: '/home', name: 'Home', component: Home, children: [ { path: 'child1', name: 'Child', component: Child } ] }, ]})复制代码
new Router时产生这个类的一个实例,路由初始化是在组件初始化阶段,调用beforeCreate钩子函数,执行router.init()初始化路由
源码分析:var VueRouter = function VueRouter (options) { if ( options === void 0 ) options = {}; ... //匹配路由对象 this.matcher = createMatcher(options.routes || [], this);//根据mode采用不同的路由方式,默认hash //共有三种模式history('/地址',需要服务器设置),hash('#地址'),abstract(非浏览器环境使用) var mode = options.mode || 'hash'; this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false; if (this.fallback) { mode = 'hash'; } if (!inBrowser) { mode = 'abstract'; } 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)); } }};init (app: any /* Vue component instance */) { process.env.NODE_ENV !== 'production' && assert( install.installed, `not installed. Make sure to call \`Vue.use(VueRouter)\` ` + `before creating root instance.` ) //添加路由实例 this.apps.push(app) // main app already initialized. //确保路由多次添加 if (this.app) { return } this.app = app const history = this.history if (history instanceof HTML5History) { history.transitionTo(history.getCurrentLocation()) } else if (history instanceof HashHistory) { const setupHashListener = () => { history.setupListeners() } //使用transitionTo完成路径切换 history.transitionTo( history.getCurrentLocation(), setupHashListener, setupHashListener ) } }复制代码
...未完待续