vueRouter实现原理
vueRouter 实现原理
前置知识
想了解vueRouter
,需要先了解vue
中的插件、混入、Vue.observable、插槽、render函数、运行时和完整版Vue的概念,如果没有了解,可以先在官网复习一波。
vueRouter两种模式
总所周知,vueRouter
分为hash
模式和history
模式;两种区别如下:
模式名称 | 实现原理 |
---|---|
Hash模式 | hash模式以url中#号后面的内容作为路径,通过监听hashchange事件获取到当前路由地址然后找到对应的组件进行渲染 |
history模式 | 通过popstate事件,根据当前路由地址找到相应的组件进行渲染(需要服务端支持) |
实现思路
先看一下我们平时使用vueRouter
的关键代码:
// router/index.js
// 1.引入VueRouter
import VueRouter from 'vue-router'
// 2.引入vue
import Vue from 'vue'
// 3. 注册路由插件
Vue.use(VueRouter)
// 4.添加路由规则
const routes = [
{
path: '/',
name: 'index',
component: Layout
},
]
// 5.创建路由对象,传入路由规则
const router = new VueRouter({routes})
// 6.导出router
export default router
// main.js
import Vue from 'vue'
import router from './router'
new Vue({
// 7. 注册 router 对象
router,
render: h => h(App)
}).$mount('#app')
- 这里我们需要解一下
Vue.use()
方法:该方法至少传入一个参数,该参数类型必须是 Object 或 Function,如果是 Object 那么这个 Object 需要定义一个 install 方法,如果是 Function 那么这个函数就被当做 install 方法。在Vue.use()
执行时 install 会默认执行,当 install 执行时第一个参数就是 Vue,其他参数是Vue.use()
执行时传入的其他参数;同时Vue.use()
会自动阻止多次注册相同插件。
了解完使用流程,我们来分析一下它的实现思路:
- 创建
VueRouter
插件,静态方法install
,在install
方法中,判断插件是否已经被加载,同时在vue
加载的时候把传入的router
对象挂载到vue
实例上。 - 创建
VueRouter
类:- 初始化
options
(记录构造函数中传入的对象)、routeMap
(记录路由地址和组件的对应关系)、data
(相应式的对象,可以记录当前路由地址,当路由地址变化时候,对应的组件做相应的更新) - 创建
initRouteMap()
方法,遍历所有路由信息,把组件和路由的映射记录到routeMap
对象中 - 注册
popstate
事件,当路由地址发生变化,重新记录当前的路径 - 创建
router-link
和router-view
组件 - 当路径改变的时候通过当前路径在
routerMap
对象中找到对应的组件,渲染router-view
- 初始化
分析完大概思路,我们就可以创建文件开始编写。
实现 install 方法
首先我们需要创建一个vueRouter
文件夹,里面创建一个index.js 文件,我们在上面分析了一下VueRouter
在使用时候有new
的操作,所以我们使用ES6
中class
来实现。具体步骤如下:
// 设置全局变量
let _Vue = null
class VueRouter {
static install(Vue) {
// 1.判断当前插件是否安装
if(VueRouter.install.isInstalled) return;
VueRouter.install.isInstalled = true
// 2.把vue构造函数记录到全局变量
_Vue = Vue
// 3.把创建vue实例的时候传入的router对象挂载到vue全局对象上
_Vue.mixin[{
beforeCreate() {
// 判断当前实例上是否有router
if(this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
}]
}
constructor(options) {
this.options = options;
this.routerMap = {}
}
}
constructor 构造函数
在constructor
构造函数中,首先我们先把传入的options
保存,然后初始化routerMap
,到时候需要便利传入进来的路由规则,然后以键值对的形式存入到routerMap
中,最后,我们需要创建一个响应式的data
对象,Vue
中提供了 observable 方法来帮助我们创建响应式的对象,使用方法也比较简单,在data
中我们设置一个变量current
来记录当前路由。
constructor(options) {
// 记录构造函数中传入的options属性
this.options = options
// 初始化routerMap,用于记录路由表
this.routerMap = {}
// 创建响应式的data对象
this.data = _Vue.observable({
// 当前路由对象,用于记录当前路由
current: '/'
})
}
createRouterMap 便利路由规则
在上一步我们已经在构造函数中初始化了routerMap
对象,这里我们需要写个方法去便利路由规则,然后存储到routerMap
中。代码如下:
createRouterMap() {
// 便利路由规则,吧路由规则挂载到routerMap上
this.$options.router.forEach(item => {
this.routerMap[item.path] = item.component
})
}
创建组件
在使用vueRouter
时,我们会用到router-link
组件和router-view
组件,这俩功能就不细说了,创建组件也是比较简单的,vue
提供了 Vue.component方法,使用方式如下:
initComponents(Vue) {
Vue.component('router-link',{
props: {
to: String
},
render(h) {
return h('a',{
attrs: {
href: this.to
},
},this.$slots.default)
},
})
Vue.component('router-view',{
render(h) {
// 获取当前路由匹配到的组件
const component = self.routerMap[self.data.current]
return h(component)
}
})
}
创建完成后,我们还需要调用一下,目前初始化函数有两个:便利路由规则方法、创建组件方法,我们可以再写一个init
方法,来调用初始化的函数:
// 初始化方法
init() {
this.createRouterMap()
this.initComponents(_Vue)
}
- 同时我们需要在
constructor
构造函数中调用一下:
constructor(options) {
this.options = options
this.routerMap = {}
this.data = _Vue.observable({
current: '/'
})
// 初始化方法
this.init()
}
接下来我们可以试着在router
文件夹中把引入的VueRouter
插件改成我们写的插件:
// router/index.js
import VueRouter from '../vueRouter'
然后运行项目看是否显示成功。
实现点击事件
前面我们已经添加了router-link
组件,接下来我们需要点击的时候去更改data
中的current
来记录当前的路径:
Vue.component('router-link',{
props: {
to: String
},
render(h) {
return h('router-link',{
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
},this.$slots.default)
},
methods: {
clickHandler(e) {
// 改变url中的路径
history.pushState({}, '', this.to)
// 修改响应式data中的current
self.data.current = this.to
// 阻止a标签默认事件
e.preventDefault()
}
}
})
我们还需要添加一个监听事件,如果点击浏览器左侧的前进和后退按钮,我们的页面也应该做出相应的变化:
initEvent() {
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}
最后在init
方法中调用一下即可
init() {
this.createRouterMap()
this.initComponents(_Vue)
this.initEvent()
}
这样一个小型的vueRouter
基本就完成了,hash
模式也比较简单,我们只需要把点击事件方法改变一下即可:
Vue.component('router-link',{
props: {
to: String
},
render(h) {
return h('router-link',{
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
},this.$slots.default)
},
methods: {
clickHandler(e) {
// 判断模式
self.options.mode == 'history' && history.pushState({}, '', this.to)
self.options.mode == 'hash' && (window.location.hash = `#${this.to}`)
self.data.current = this.to
e.preventDefault()
}
}
})
同时修改一下监听事件
initEvent() {
if(this.options == 'history') {
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}else{
window.addEventListener('hashchange', () => {
this.data.current = window.location.hash.substr(1) ? window.location.hash.substr(1) : '/'
})
}
}
结尾
这样我们的
hash
模式和history
模式就实现完成了,关于一些细节方面的东西就不进行模拟了,感兴趣的可以尝试实现一下。