前言
作为vue全家桶的一员,vue-router一直是广大前端开发者平常离不开的工具库之一。今天我们就来一起来了解一下vue-rouer。
快速说明
Vue Router 是 Vue.js (opens new window)官方的路由管理器。
官方地址:router.vuejs.org/zh/。
使用
import Vue from 'vue'
// 引入vue-router
import VueRouter from 'vue-router'
// 通过use激活使用
Vue.use(VueRouter)
const router = new VueRouter({
routes:[
//...路由配置
]
});
// 在实例化vue的时候把router传入。
const app = new Vue({
router
}).$mount('#app')
复制代码
Vue.use(VueRouter)
再来看看我们在use的时候,vue-router做了什么。因为vue.use会调用传入对象的install方法。我们就从install入手。
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)
}
}
// 全局mixin
Vue.mixin({
beforeCreate () {
if (isDef(this.$options.router)) {
this._routerRoot = this
this._router = this.$options.router
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和$route
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
// 注册组件RouterView和RouterLink
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
}
复制代码
可以看出,当我们在调用Vue.use(VueRouter)的时候,主要做了以下几件事:
- 全局mixin,页面每次渲染时,会把组件赋值给组件的this._routerRoot。在组件销毁时,注销注册。
- 数据劫持Vue.prototype. route
- 注册了RouterView和RouterLink。
VueRouter类
前面提到,我们在用vue-router的时候需要先new一个VueRoute实例,然后传入app中。那这个VueRoute类是怎样的呢?
export default class VueRouter {
// ...
constructor (options) {
// 存储路由信息
this.app = null
this.apps = []
this.options = options
this.beforeHooks = []
this.resolveHooks = []
this.afterHooks = []
this.matcher = createMatcher(options.routes || [], this)
let mode = options.mode || 'hash'
this.fallback =
mode === 'history' && !supportsPushState && options.fallback !== false
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract'
}
this.mode = mode
// 针对不同的mode 生成对应的history实例。
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}`)
}
}
}
// ...
}
复制代码
vue-router如何监听路由变化
相信大家都知道vue-router支持2种mode,hash和history。而他们是怎么监听的呢?
首先,vue-router的实例中会记录这很多信息,其中包括我们传入的路由配置。
export default class VueRouter {
// ....
constructor (options) {
this.app = null
this.apps = []
this.options = options
this.beforeHooks = []
this.resolveHooks = []
this.afterHooks = []
this.matcher = createMatcher(options.routes || [], this)
// ...
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}`)
}
}
}
// ....
}
复制代码
hash
先来看看hash的处理,
export class HashHistory extends History {
//...
// 当vue app挂载时会触发
setupListeners () {
if (this.listeners.length > 0) {
return
}
const router = this.router
const expectScroll = router.options.scrollBehavior
const supportsScroll = supportsPushState && expectScroll
if (supportsScroll) {
this.listeners.push(setupScroll())
}
// 路径变化回调
const handleRoutingEvent = () => {
const current = this.current
// 确保hash的路径正确
if (!ensureSlash()) {
return
}
// 路由判断并跳转
this.transitionTo(getHash(), route => {
if (supportsScroll) {
handleScroll(this.router, route, current, true)
}
if (!supportsPushState) {
replaceHash(route.fullPath)
}
})
}
// 如果浏览器支持popstate就监听popstate事件,如果不支持就监听hashchange事件
const eventType = supportsPushState ? 'popstate' : 'hashchange'
window.addEventListener(
eventType,
handleRoutingEvent
)
this.listeners.push(() => {
window.removeEventListener(eventType, handleRoutingEvent)
})
}
//...
}
复制代码
history
接下来是history的源码,记住history模式需要依赖HTML5的History API。
关于History API:developer.mozilla.org/zh-CN/docs/…
export class HTML5History extends History {
// ...
setupListeners () {
// ...
const router = this.router
const expectScroll = router.options.scrollBehavior
const supportsScroll = supportsPushState && expectScroll
if (supportsScroll) {
this.listeners.push(setupScroll())
}
// 路径变化回调
const handleRoutingEvent = () => {
const current = this.current
// 避免在某些浏览器中手册渲染会触发popstate事件
const location = getLocation(this.base)
if (this.current === START && location === this._startLocation) {
return
}
// 判断路由并跳转
this.transitionTo(location, route => {
if (supportsScroll) {
handleScroll(router, route, current, true)
}
})
}
window.addEventListener('popstate', handleRoutingEvent)
this.listeners.push(() => {
window.removeEventListener('popstate', handleRoutingEvent)
})
}
//...
}
复制代码
可以看出history是用popstate事件监听路由的。
页面是如何渲染的?
我们看到vue-router实例的工具其实是记录了大量的信息(包括我们的路由配置,还有一些当前路由下的信息),但我们的页面是怎么渲染的呢?
既然要用vue-router,就一定会用到router-view组件,而这个组件就是负责渲染的。
export default {
name: 'RouterView',
functional: true,
props: {
name: {
type: String,
default: 'default'
}
},
render (_, { props, children, parent, data }) {
// ...
let inactive = false
// ...
// 如果用了keep-alive 会把component缓存
if (inactive) {
const cachedData = cache[name]
const cachedComponent = cachedData && cachedData.component
if (cachedComponent) {
if (cachedData.configProps) {
fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps)
}
return h(cachedComponent, data, children)
} else {
// 渲染之前的缓存组件
return h()
}
}
// ...
return h(component, data, children)
}
复制代码
router-view本身就是一个组件,他的render中维持一个缓存,当搭配了keep-alive之后会把展示过的组件缓存起来。下次如果再渲染时可以直接用。
总结
今天我们了解了vue-router的机制逻辑。大致为,先通过Vue.use注册一些组件,以及监听Vue.prototype.$router等。当通过new创建router实例的时候,router实例中会维持我们的路由信息,并根据mode生成history实例。页面配合router-view组件实现渲染。希望可以帮助大家有个大致的了解帮助。