前言
路由最初是由后端提出,浏览器发出请求,服务器根据路由的配置,返回相应信息。后来随着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
)
}
}
复制代码
...未完待续