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) return
install.installed = true //为了确保install逻辑只执行一次
_Vue = Vue //用全局的_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)
}
}
//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.optionMergeStrategies
strats.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 !== false
if (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)
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}`)
}
}
}
//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路由
//定义了setupHashListener
const 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.currentRoute
if (!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.fullPath
const base = this.history.base
const 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 } = 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]
}
}
}
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计算出一个新的路径。