v-router的作用就是根据不同的路径映射到不同的视图。
一、例子
<div id="app"><h1>hello App!</h1><p><!--使用router-link组件来导航--><!--通过传入to属性来指定连接--><!--<router-link>默认会被渲染成一个<a>标签--><router-link to="/foo">Go to Foo</router-link><router-link to="/bar">Go to Bar</router-link></p><!--路由出口--><!--路由匹配到组件将渲染在这里--><router-view></router-view></div>
</body>
<script src="vue.js"></script>
<script src="vue-router.js"></script>
<script>//0.如果使用模块化编程,导入Vue和VueRouter,要调用Vue.use(VueRouter)//1.定义(路由)组件const Foo = {template:'<div>foo</div>'};const Bar = {template:'<div>bar</div>'};//2.定义路由//每一个路由应该映射一个组件。其中'component'可以是通过Vue.extend()创建的组件构造器,或者是一个组件配置对象。const routes = [{path:'/foo',component:Foo},{path:'/bar',component:Bar}];//3.创建路由实例,然后传'routes'配置const router = new VueRouter({routes:routes});//4.创建和挂载根实例//记得要通过router配置参数注入路由,从而让整个应用都有路由功能const app = new Vue({router:router}).$mount('#app');
</script>复制代码
二、路由注册
Vue通用插件注册原理
Vue提供了Vue.use的全局API来注册这些插件。
Vue.use接收一个plugin参数,维护一个installedPlugins数组,它存储所有注册过的plugin;接着会判断plugin有没有定义install方法,如果有则调用该方法,并且该方法执行的第一个参数是Vue,这样不需要从外导入import Vue;最后把plugin存储到installedPlugins中。
三、路由安装
export let _Vue
export function install (Vue) {if (install.installed && _Vue === Vue) returninstall.installed = true //为了确保install逻辑只执行一次_Vue = Vue //用全局的_Vue来接收参数Vueconst isDef = v => v !== undefinedconst registerInstance = (vm, callVal) => {let i = vm.$options._parentVnodeif (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {i(vm, callVal)}}//Vue-router安装的最重要一步就是利用Vue.mixin去把beforeCreate和destroyed钩子函数注入到每一个组件中Vue.mixin({beforeCreate () {if (isDef(this.$options.router)) {this._routerRoot = this //根Vue实例this._router = this.$options.router //this._router表示VueRouter的实例router,它是在new Vue的时候传入的。this._router.init(this) //初始化router 在new Vue时,把router作为配置的属性传入,//然后调用new VueRouter返回router实例,再对router进行初始化//把this._router变成响应式对象Vue.util.defineReactive(this, '_route', this._router.history.current)} else {this._routerRoot = (this.$parent && this.$parent._routerRoot) || this}registerInstance(this, this)},destroyed () {registerInstance(this)}})//在Vue原型上定义了$router和$route两个属性的get方法,这就是为什么我们可以在组件实例上访问this.$router以及this.$route。Object.defineProperty(Vue.prototype, '$router', {get () { return this._routerRoot._router }})Object.defineProperty(Vue.prototype, '$route', {get () { return this._routerRoot._route }})//定义了两个全局组件Vue.component('RouterView', View)Vue.component('RouterLink', Link)const strats = Vue.config.optionMergeStrategiesstrats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}复制代码
Vue.mixin函数
四、VueRouter
export default class VueRouter {static install: () => void;static version: string;app: any;apps: Array<any>;ready: boolean;readyCbs: Array<Function>;options: RouterOptions;mode: string;history: HashHistory | HTML5History | AbstractHistory;matcher: Matcher;fallback: boolean;beforeHooks: Array<?NavigationGuard>;resolveHooks: Array<?NavigationGuard>;afterHooks: Array<?AfterNavigationHook>;//执行new VueRouter()的时候做了哪些事情?constructor (options: RouterOptions = {}) {this.app = null //根Vue实例this.apps = [] //保存持有$options.router属性的Vue实例this.options = options //保存传入的路由配置this.beforeHooks = []this.resolveHooks = []this.afterHooks = []this.matcher = createMatcher(options.routes || [], this) //路由匹配器let mode = options.mode || 'hash'//this.fallback表示在浏览器不支持history.pushState的情况下,根据传入的fallback配置参数,//决定它是否退回到hash模式this.fallback = mode === 'history' && !supportsPushState && options.fallback !== falseif (this.fallback) {mode = 'hash'}if (!inBrowser) {mode = 'abstract'}this.mode = mode//三种类型的路由:history、hash、abstract//实例化路由后会返回router实例switch (mode) {case 'history':this.history = new HTML5History(this, options.base)breakcase 'hash':this.history = new HashHistory(this, options.base, this.fallback)breakcase 'abstract':this.history = new AbstractHistory(this, options.base)breakdefault:if (process.env.NODE_ENV !== 'production') {assert(false, `invalid mode: ${mode}`)}}}//match方法match (raw: RawLocation,current?: Route,redirectedFrom?: Location): Route {return this.matcher.match(raw, current, redirectedFrom)}get currentRoute (): ?Route {return this.history && this.history.current}//初始化函数 传入参数为Vue实例init (app: any) {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) //然后存储到this.apps中if (this.app) { return}this.app = app //只有根Vue实例会保存在app中const history = this.history //然后拿到当前的路由if (history instanceof HTML5History) {history.transitionTo(history.getCurrentLocation())} else if (history instanceof HashHistory) {//hash路由//定义了setupHashListenerconst setupHashListener = () => {history.setupListeners()}//执行了transitionTo方法 该调用了match函数history.transitionTo(history.getCurrentLocation(),setupHashListener,setupHashListener)}history.listen(route => {this.apps.forEach((app) => {app._route = route})})}beforeEach (fn: Function): Function {return registerHook(this.beforeHooks, fn)}beforeResolve (fn: Function): Function {return registerHook(this.resolveHooks, fn)}afterEach (fn: Function): Function {return registerHook(this.afterHooks, fn)}onReady (cb: Function, errorCb?: Function) {this.history.onReady(cb, errorCb)}onError (errorCb: Function) {this.history.onError(errorCb)}push (location: RawLocation, onComplete?: Function, onAbort?: Function) {this.history.push(location, onComplete, onAbort)}replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {this.history.replace(location, onComplete, onAbort)}go (n: number) {this.history.go(n)}back () {this.go(-1)}forward () {this.go(1)}getMatchedComponents (to?: RawLocation | Route): Array<any> {const route: any = to? to.matched? to: this.resolve(to).route: this.currentRouteif (!route) {return []}return [].concat.apply([], route.matched.map(m => {return Object.keys(m.components).map(key => {return m.components[key]})}))}resolve (to: RawLocation,current?: Route,append?: boolean): {location: Location,route: Route,href: string,normalizedTo: Location,resolved: Route} {const location = normalizeLocation(to,current || this.history.current,append,this)const route = this.match(location, current)const fullPath = route.redirectedFrom || route.fullPathconst base = this.history.baseconst href = createHref(base, fullPath, this.mode)return {location,route,href,normalizedTo: location,resolved: route}}addRoutes (routes: Array<RouteConfig>) {this.matcher.addRoutes(routes)if (this.history.current !== START) {this.history.transitionTo(this.history.getCurrentLocation())}}
}复制代码
五、matcher
Matcher返回了两个方法:match和addRoutes。
match方法的作用:location和route做匹配。
Location:
例子:/abc?foo=bar&baz=qux#hello
,它的 path
是 /abc
,query
是 {foo:bar,baz:qux}
Route:
Route它除了描述了类似 Loctaion
的 path
、query
、hash
这些概念,还有 matched
表示匹配到的所有的 RouteRecord。
createMatcher
export function createMatcher (
routes: Array<RouteConfig>, //new VueRouter返回的实例router: VueRouter //用户自定义的路由配置
): Matcher {
//首先执行createRouteMap 创建一个路由映射表const { pathList, pathMap, nameMap } = createRouteMap(routes)function addRoutes (routes) {createRouteMap(routes, pathList, pathMap, nameMap)}//match方法接受三个参数 它的作用是根据传入的raw和当前的路径currentRoute计算出一个新的路径并返回function match (raw: RawLocation,currentRoute?: Route, //当前路径redirectedFrom?: Location //重定向): Route {const location = normalizeLocation(raw, currentRoute, false, router)const { name } = locationif (name) {const record = nameMap[name]if (process.env.NODE_ENV !== 'production') {warn(record, `Route with name '${name}' does not exist`)}if (!record) return _createRoute(null, location)const paramNames = record.regex.keys.filter(key => !key.optional).map(key => key.name)if (typeof location.params !== 'object') {location.params = {}}if (currentRoute && typeof currentRoute.params === 'object') {for (const key in currentRoute.params) {if (!(key in location.params) && paramNames.indexOf(key) > -1) {location.params[key] = currentRoute.params[key]}}}if (record) {location.path = fillParams(record.path, location.params, `named route "${name}"`)return _createRoute(record, location, redirectedFrom)}} else if (location.path) {location.params = {}for (let i = 0; i < pathList.length; i++) {const path = pathList[i]const record = pathMap[path]if (matchRoute(record.regex, location.path, location.params)) {return _createRoute(record, location, redirectedFrom)}}}return _createRoute(null, location)}// ...function _createRoute (record: ?RouteRecord,location: Location,redirectedFrom?: Location): Route {if (record && record.redirect) {return redirect(record, redirectedFrom || location)}if (record && record.matchAs) {return alias(record, location, record.matchAs)}return createRoute(record, location, redirectedFrom, router)}return {match,addRoutes}
}复制代码
createRouteMap
函数的目标是把用户的路由配置转换成一张路由映射表。它的创建是通过遍历 routes
为每一个 route
执行 addRouteRecord
方法生成一条记录。
addRoutes
addRoutes的作用是动态添加路由配置。
六、逻辑图
v-root是vue的一款插件,它是通过Vue.use方法注册路由插件的。
在Vue.use(VueRouter)阶段,调用Vue.use中的install方法进行路由安装。install方法中有两个钩子函数:beforeCreate和destroyed;另外install中还定义了router和route,这样使得可以在程序中访问this.$router和this.$route;install中还定义了两个全局的API,rootLink和rootView。
BeforeCreate钩子函数发生在new Vue()阶段,其参数为new VueRouter()返回的参数。然后调用init函数初始化路由,调用defineReactive将其变为响应式对象。
VueRouter中定义了三中路由:hash、history、abstract。
以hash路由为例:执行transitionTo路由切换,然后进行路由匹配match。路由匹配方法它是定义在Matcher类中的,该类中主要定义了createMatcher方法、addRoutes方法、和match方法。createMatcher方法是用来把用户的路由配置转换成一张路由映射表,addRoutes方法的作用是动态添加路由配置,match放法的作用是根据raw和currentrouter计算出一个新的路径。