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 parameters
const 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 hooks
strats.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: () => void
static version: string
static isNavigationFailure: Function
static NavigationFailureType: any
static START_LOCATION: Route
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>
constructor (options: RouterOptions = {}) {
if (process.env.NODE_ENV !== 'production') {
warn(this instanceof VueRouter, `Router must be called with the new operator.`)
}
this.app = null
this.apps = []
this.options = options
this.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'//判断模式是哈希还是history
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false // 判断浏览器是否支持history,如果不支持则回退到hash模式;
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract'
}
this.mode = mode
switch (mode) {// 根据不同模式创建对应的history实例
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:
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-line
createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent)
// add aliases of parent
if (parent && parent.alias.length) {
createRouteMap(
// $flow-disable-line route is defined if parent is
parent.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 } = location
if (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.redirect
let redirect = typeof originalRedirect === 'function'
? originalRedirect(createRoute(record, location, null, router))
: originalRedirect
if (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 = redirect
const { name, path } = re
let { query, hash, params } = location
query = re.hasOwnProperty('query') ? re.query : query
hash = re.hasOwnProperty('hash') ? re.hash : hash
params = re.hasOwnProperty('params') ? re.params : params
if (name) {
// resolved named direct
const 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 redirect
const rawPath = resolveRecordPath(path, record)
// 2. resolve params
const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
// 3. rematch with existing query and hash
return 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.matched
const aliasedRecord = matched[matched.length - 1]
location.params = aliasedMatch.params
return _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 0
params[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)
}
后面的读不动了 有空继续