用几十行代码分析路由的原理
let Vue
/* 定义一个类,记录当前浏览器url的path或者hash */
class History {
constructor () {
this.current = null
}
}
/* 当我们new Router({...})时,主要发生了以下事情 */
class Router {
constructor (options) {
this.mode = options.mode || 'hash'
/*用来存放每个路由的数组*/
this.routes = options.routes || []
/*
路由数组的一个映射,会把 {component: App, path: '/app', name: 'App'} 这样的对象映射成 {app: App}
*/
this.routeMap = this.createMap(this.routes)
this.history = new History()
this.init()
}
init () {
if(this.mode === 'hash') { //hash模式
window.location.hash = window.location.hash ? '' : '/' //判断url中是否有hash,有的话不做任何操作,如果没有就跳到根路径
window.addEventListener('load', () => {
this.history.current = location.hash.slice(1) //用上面定义的History类来记录当前url的hash值
})
window.addEventListener('hashchange', () => {
this.history.current = location.hash.slice(1) //当url的hash值改变时,重新记录
})
}else { //history模式,原理与hash一样
window.location.pathname = window.location.pathname ? '' : '/'
window.addEventListener('load', () => {
this.history.current = location.pathname.slice(1)
})
window.addEventListener('popstate', () => {
this.history.current = location.pathname.slice(1)
})
}
}
/* 映射函数 */
createMap (routes) {
routes.reduce((prev, next) => {
prev[next.path] = next.component
return prev
},{})
}
}
/* Vue.use(Router)时会调用Router.install函数 */
Router.install = function (_Vue) {
Vue = _Vue
/* 用Vue.mixin混入生命周期函数 */
Vue.mixin({
beforeCreate () {
if(this.$options && this.$options.router) {
this._root = this //存放Vue实例
this._root._router = this.$options.router //存放我们传进去的router数组
Vue.util.defineReactive(this, '', this._root._router.history.current) //Vue内部封装的响应式函数,原理也是defineProperty这个函数,当url变化时会重新渲染页面
}else {
/* 子组件是没有options的,为了让每个组件都能访问this.$router this.$route,把子组件的_root指向父组件的_root */
this._root = this.$parent._root
}
/* 把$router $route代理到Vue实力上 */
Object.defineProperty(this, '$router', {
get () {
return this._root._router
}
})
Object.defineProperty(this, '$route', {
get () {
return {current: this._root._router.history.current}
}
})
}
})
/* 定义两个全局组件 */
Vue.component('router-link', {
props: {
to: String
},
/* h函数会生成一个虚拟dom,
也可以用jsx语法 <a to = { this.to }> { this.slots.default } </a>
*/
render (h) {
return h('a', { attr: { href: this.to } }, this.slots.default)
}
})
Vue.component('router-view', {
/* 上面的映射函数就是为了这一步,
映射生成的 {app: App }这样的对象,可以通过routeMap[current]获得当前的url路径('/app')对应的Component(App),
然后传给h函数生成虚拟dom */
render (h) {
let routeMap = Vue.self._root._router.routeMap
let current = Vue.self._root._router.history.current
return h(routeMap[current])
}
})
}