VueRouter 的实现原理
- 通过pushState方法改变地址栏并将改变的地址添加到历史记录中
- 通过监听popstate事件在触发事件后更新页面Dom
- 从而实现单页面的路由切换以及页面加载
下面详细讲解一下VueRouter的具体代码实现过程
先梳理一下实现步骤
1. 创建VueRouter类并默认导出
2. 因为Vue.use 会执行类的install方法,所以需要定义一个install静态方法
3. 在构造函数中接收传入的option对象, 并挂载到类上
4. 创建一个用于存储VueRouter当前路径的响应式对象
5. 在实例上挂载一个RouterMap用于记录 VueRouter路径中, path和component的对应关系
6. 将传入的VueRouter对象数组转为key Value对象关系
7. 创建Router-link和Router-View组件,并注册到Vue实例上
初始化代码环境
- 环境是Vue2 加 VueRouter 的vue-cli创建的环境
- 创建src/vueRouter/index.js 用于书写我们自己的VueRouter
- 本次写的示例代码主要替代环境中的Vue-Router来实现Vue的路由切换功能
环境创建选项

书写代码实现步骤
let _Vue = null
export default class VueRouter {
};
static install (Vue) {
_Vue = Vue
}
constructor (option) {
this.option = option
this.data = _Vue.observable({
current: '/'
})
this.routerMap = {}
this.createRouterMap()
this.initComponets()
}
createRouterMap = () => {
this.option.routes.forEach(route => {
this.routerMap[route.path] = route.component
})
}
initComponets = () => {
_Vue.component('router-link', {
props: 'to',
template: '<a :href="to"><slot></slot></a>'
})
_Vue.component('router-view', {
render (h) {
return h(this.routerMap[this.data.current])
}
})
}
- 上面我们已经将前期的准备工作做完了,那我们如何来将这些方法挂载到Vue实例上呢
- 我们来分析一下步骤
- 首先我们需要获取到Vue的实例才能进行操作,那在哪里能获取到Vue的实例呢
import Vue from 'vue'
import VueRouter from '../vueRouter'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import( '../views/About.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
此时基本的条件我们已经写好了,我们将引入的vue-Router改成我们写的VueRouter
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Vue from 'vue'
import VueRouter from '../vueRouter'
import Home from '../views/Home.vue'
这个时候页面就会出现以下的错误

- 这时候我们稳住,不要慌,一个问题一个问题来
- 第一个问题是说我们正在使用的Vue版本是运行时版本,其中模板编译器不可用
- 那为什么会提示这个问题呢?
_Vue.component('router-link', {
props: 'to',
template: '<a :href="to"><slot></slot></a>'
})
- 是因为我们上面这个地方使用了template模板
- 当前我们的Vue版本是运行时版本,它不认识这个模板,你需要加载完整版Vue才带有编译器
- 不过这样会让Vue的大小增加10k, 一般我们的项目都是使用运行版Vue, 因为有Webpack帮我们预编译
- 所以我们不会关注到这个问题
- 这里我们就先使用完整版Vue来解决这个问题
- 在Vue-cli的配置参考中有说明如何开启完整版Vue 官网路径 https:
-
module.exports = {
runtimeCompiler: true
}
_Vue.component('router-view', {
this.routerMap[this.data.current]
render (h) {
return h(this.routerMap[this.data.current])
}
})
const self = this
_Vue.component('router-view', {
render (h) {
return h(self.routerMap[self.data.current])
}
})
这里我们可以看到页面已经没有错误了,页面也显示了组件内容了

- 这个时候虽然可以看到页面效果了,但是点击无法正常的切换页面
- 而且点击浏览器还会转圈,说明我们是向服务器发起请求了, Vue正常页面切换是不会刷新浏览器的所以这也是个问题
_Vue.component('router-link', {
props: {
to: String
},
template: '<a :href="to" @click="clickHandle"><slot></slot></a>',
methods: {
clickHandle (e) {
e.preventDefault()
self.data.current = this.to
window.history.pushState({}, '', this.to)
}
}
})
到这里基本路由切换就完成了

但是这里还有一个问题

- 浏览器的这个返回前进这个时候只能改变地址栏的URL, 页面并不会进行切换
- 这里我们来分析以下,这里的前进后退是在历史列表中查找上一个或者下一个
- 它的改变我们写的VueRuoter并不知道,所以才会有这个问题
- 知道问题我们就来解决它
initEvent = () => {
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}
constructor (option) {
this.option = option
this.data = _Vue.observable({
current: '/'
})
this.routerMap = {}
this.createRouterMap()
this.initComponets()
this.initEvent()
}
这个时候我们的VueRouter的基本功能就大功告成了
