vue-router简介
vue-router工作原理:
- url改变
- 触发监听事件 (原理见路由模式)
- 改变vue-router里面的current变量
- vue监听current的监听者
- 获取到新的组件
- render新组件
vue-router如何实现无刷新页面切换:
- 采用某种方式使url发生改变。这种方式可能是调用HTML5 history API实现,也可能是点击前进后退或者改变路由hash.但是不管采用哪种方式,它都不能造成浏览器刷新,仅仅只是单纯的url发生变化.
- history.pushState(state,title,url) :
无刷新的向当前history插入一条历史状态,但是并不会造成页面的重新加载和浏览器向服务器发送请求 - window.onpopState() :
当点击后退,前进按钮,或调用history.go()方法时,该方法会被触发,但是history.pushState并不会直接触发popstate.
回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前 URL 所提供的状态对象(即这两个方法的第一个参数)这个state对象也可以直接通过history对象读取
window.onpopstate = function (event) {//console.log('location: ' + document.location);console.log('state: ' + JSON.stringify(event.state));
};
- 监听到url的变化之后,根据不同的路径获取渲染内容,再把内容填充到div容器里.从上面案例可知,监听url的变化一般在两个地方,第一是在window.onpopstate包裹的回调函数里,第二是在执行history.pushState或history.replaceState的后面. render函数根据跳转路径的不同动态改变app容器里面的内容,从而便模拟出了点击不同路径页面似乎发生了跳转的效果.
- render:function(createElement){return createElement(APP)} 其中的形参是一个方法,作用是根据给定的组件渲染,把给定的组件渲染到el区域中
路由模式
-
hash模式
哈希路径中带个#,#后面的就是hash的内容;
可以通过location.hash拿到
可以通过onhashchange监听hash的改变 -
history模式
正常路径,没有#
可以通过location.pathname拿到
可以通过onpopstate监听history的改变
深入源码
例子
先来看看在vue中怎么用vue-router
import vue和vue-router后,
用vue.use(vueRouter)来注册组件
而这个vue.use函数又会执行vue-router.install函数
import Vue from 'vue'
import VueRouter from '../c-router' // 引用
import home from '../views/Home.vue'Vue.use(VueRouter)//1 注册插件
//作用
//1. 执行里面方法
//2. 如果这个方法有一个属性install 并且这个属性是一个方法, Vue.use就会执行install方法
// 如果没有install方法, 就会执行这个父级方法(vuerouter)本身
//3. install这个方法的第一参数是vue(就是vue的构造函数)const routes = [{path: '',name: 'Layout',children: [{path: '/home',name: 'Home',component: Home},{path: '/about',name: 'About',component: () => import('../viewa/About.vue')}]}
]const router = new VueRouter({mode:'hash',routes
})
vue中怎么注册vue-router
再来看看源码
先来看看vue2源码中的initUse函数,其中声明了Vue.use函数
路径:src/core/global-api/use.ts
import type { GlobalAPI } from 'types/global-api'
import { toArray, isFunction } from '../util/index'export function initUse(Vue: GlobalAPI) {
//注意看这里的Vue.use, 就是 之前vue中怎么调用vue-router 小节中,我们用到的Vue.use(VueRouter)Vue.use = function (plugin: Function | any) {//plugin:插件//重复注册插件的情况:const installedPlugins = this._installedPlugins || (this._installedPlugins = [])if (installedPlugins.indexOf(plugin) > -1) {return this//若已经注册过(用indexof能找到),直接返回}// additional parametersconst args = toArray(arguments, 1)//args就是之前vue中怎么调用vue-router 小节中的install的参数args.unshift(this) //this就是vue的实例if (isFunction(plugin.install)) {//如果plugin的install属性是方法的话plugin.install.apply(plugin, args)//调用} else if (isFunction(plugin)) {//若无plugin.apply(null, args)}installedPlugins.push(plugin)return this}
}
vue-router中install方法实现 https://github.com/vuejs/vue-router/tree/dev
路径:dist/vue-router.js
function install (Vue) {//判断是否已经安装过插件if (install.installed && _Vue === Vue) { return }install.installed = true;_Vue = Vue;
//辅助函数,判断一个值是否已定义var isDef = function (v) { return v !== undefined; };
//注册路由实例的辅助函数var registerInstance = function (vm, callVal) {var i = vm.$options._parentVnode;if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {i(vm, callVal);}};//全局混入,在组件的beforeCreate和destroyed生命狗子里执行一些操作Vue.mixin({beforeCreate: function beforeCreate () {if (isDef(this.$options.router)) {//如果组件定义了$options.router,则表示当前组件是根组件this._routerRoot = this;this._router = this.$options.router;this._router.init(this);//初始化路由Vue.util.defineReactive(this, '_route', this._router.history.current);//定义响应式的_route属性,有了这个响应式的路由对象,就可以在路由更新的时候及时的通知RouterView去更新组件了} else {//如果不是根组件,则将_routerRoot指向最近的父级根组件this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;}registerInstance(this, this);// 注册路由实例},destroyed: function destroyed () {registerInstance(this);// 销毁时注销路由实例}});// 在Vue原型上定义$router属性的访问器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);// 注册RouterView组件Vue.component('RouterLink', Link);// 注册RouterLink组件var strats = Vue.config.optionMergeStrategies;// use the same hook merging strategy for route hooksstrats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;}
至此,Vue.use(VueRouter)//1 注册插件 这一步中的相关源码都看完了,我们继续往下
VueRouter类的实例化
来看看vueRouter是怎么实例化的,
路径:src/router.js
import { install } from './install'
import { START } from './util/route'
import { assert, warn } from './util/warn'
import { inBrowser } from './util/dom'
import { cleanPath } from './util/path'
import { createMatcher } from './create-matcher'
import { normalizeLocation } from './util/location'
import { supportsPushState } from './util/push-state'
import { handleScroll } from './util/scroll'
import { isNavigationFailure, NavigationFailureType } from './util/errors'
import { HashHistory } from './history/hash'
import { HTML5History } from './history/html5'
import { AbstractHistory } from './history/abstract'
import type { Matcher } from './create-matcher'export default class VueRouter {static install: () => voidstatic version: stringstatic isNavigationFailure: Functionstatic NavigationFailureType: anystatic START_LOCATION: Routeapp: anyapps: Array<any>ready: booleanreadyCbs: Array<Function>options: RouterOptionsmode: stringhistory: HashHistory | HTML5History | AbstractHistorymatcher: Matcherfallback: booleanbeforeHooks: Array<?NavigationGuard>resolveHooks: Array<?NavigationGuard>afterHooks: Array<?AfterNavigationHook>constructor (options: RouterOptions = {}) {if (process.env.NODE_ENV !== 'production') {warn(this instanceof VueRouter, `Router must be called with the new operator.`)}this.app = nullthis.apps = []this.options = optionsthis.beforeHooks = []this.resolveHooks = []this.afterHooks = []// 创建路由匹配实例;传人我们定义的routes:包含path和component的对象;以防你们忘了是啥,下面是一个新建router的例子// const router = new VueRouter({// mode: 'history',// routes: [{ path: '/',// component: Main, }],//});this.matcher = createMatcher(options.routes || [], this)let mode = options.mode || 'hash'//判断模式是哈希还是historythis.fallback = mode === 'history' && !supportsPushState && options.fallback !== false // 判断浏览器是否支持history,如果不支持则回退到hash模式;if (this.fallback) {mode = 'hash'}if (!inBrowser) {mode = 'abstract'}this.mode = modeswitch (mode) {// 根据不同模式创建对应的history实例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}`)}}}
来看看createMatcher方法
路径:src/create-matcher.js
import type VueRouter from './index'
import { resolvePath } from './util/path'
import { assert, warn } from './util/warn'
import { createRoute } from './util/route'
import { fillParams } from './util/params'
import { createRouteMap } from './create-route-map'
import { normalizeLocation } from './util/location'
import { decode } from './util/query'export type Matcher = {match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;addRoutes: (routes: Array<RouteConfig>) => void;addRoute: (parentNameOrRoute: string | RouteConfig, route?: RouteConfig) => void;getRoutes: () => Array<RouteRecord>;
};// routes为我们初始化VueRouter的路由配置;router就是我们的VueRouter实例;
export function createMatcher (routes: Array<RouteConfig>,router: VueRouter
): Matcher {const { pathList, pathMap, nameMap } = createRouteMap(routes)//根据新的routes生成路由;pathList是根据routes生成的path数组;pathMap是根据path的名称生成的map;如果我们在路由配置上定义了name,那么就会有这么一个name的Map;//添加路由function addRoutes (routes) {createRouteMap(routes, pathList, pathMap, nameMap)}function addRoute (parentOrRoute, route) {const parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined// $flow-disable-linecreateRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent)// add aliases of parentif (parent && parent.alias.length) {createRouteMap(// $flow-disable-line route is defined if parent isparent.alias.map(alias => ({ path: alias, children: [route] })),pathList,pathMap,nameMap,parent)}}function getRoutes () {return pathList.map(path => pathMap[path])}//路由匹配函数,根据给定的路由地址信息进行匹配,并返回匹配到的路由对象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]}}}
// 填充路由参数到路由路径中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)}}}// no match 没有匹配的路由,创建一个空的路由对象return _createRoute(null, location)}function redirect (record: RouteRecord,location: Location): Route {const originalRedirect = record.redirectlet redirect = typeof originalRedirect === 'function'? originalRedirect(createRoute(record, location, null, router)): originalRedirectif (typeof redirect === 'string') {redirect = { path: redirect }}if (!redirect || typeof redirect !== 'object') {if (process.env.NODE_ENV !== 'production') {warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)}return _createRoute(null, location)}const re: Object = redirectconst { name, path } = relet { query, hash, params } = locationquery = re.hasOwnProperty('query') ? re.query : queryhash = re.hasOwnProperty('hash') ? re.hash : hashparams = re.hasOwnProperty('params') ? re.params : paramsif (name) {// resolved named directconst targetRecord = nameMap[name]if (process.env.NODE_ENV !== 'production') {assert(targetRecord, `redirect failed: named route "${name}" not found.`)}return match({_normalized: true,name,query,hash,params}, undefined, location)} else if (path) {// 1. resolve relative redirectconst rawPath = resolveRecordPath(path, record)// 2. resolve paramsconst resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)// 3. rematch with existing query and hashreturn match({_normalized: true,path: resolvedPath,query,hash}, undefined, location)} else {if (process.env.NODE_ENV !== 'production') {warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)}return _createRoute(null, location)}}function alias (record: RouteRecord,location: Location,matchAs: string): Route {const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`)const aliasedMatch = match({_normalized: true,path: aliasedPath})if (aliasedMatch) {const matched = aliasedMatch.matchedconst aliasedRecord = matched[matched.length - 1]location.params = aliasedMatch.paramsreturn _createRoute(aliasedRecord, location)}return _createRoute(null, location)}//根据给定的路由记录(record)、路由地址信息(location)和重定向来源(redirectedFrom)来创建一个路由对象(Route)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,addRoute,getRoutes,addRoutes}
}function matchRoute (regex: RouteRegExp,path: string,params: Object
): boolean {const m = path.match(regex)if (!m) {return false} else if (!params) {return true}for (let i = 1, len = m.length; i < len; ++i) {const key = regex.keys[i - 1]if (key) {// Fix #1994: using * with props: true generates a param named 0params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i]}}return true
}function resolveRecordPath (path: string, record: RouteRecord): string {return resolvePath(path, record.parent ? record.parent.path : '/', true)
}
后面的读不动了 有空继续