前置知识
- 插件
- 混入
- Vue.observable()
- 插槽
- render函数
- 运行时和完整版的vue
首先使用vue-cli创建一个项目
vue create my-vue-router
新增router文件夹,在router文件夹中的index.js添加基础的路由配置
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
// 注册插件
Vue.use(VueRouter)
const routes = [
{ path: '/', name: 'Home', component: Home},
{ path: '/about', name: 'About', component: () => import(/* webpackChuckName: "about" */ '../views/About.vue')}
]
// 创建路由对象
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
接下来我们再来看一个画好的VueRouter的类图
- options:记录构造函数中传入的对象
- routeMap:用来记录我们路由地址和组件的对应关系, 我们会把路由规则解析到routeMap里面来
- data: 是一个对象,里面有一个current属性,用来记录当前路由地址,设置data的目的是因为我们需要一个响应式的对象
- constructor帮我们初始化上面的几个属性
- init方法用来调用图上所示init下面的3个方法
- initEvent方法用来注册popstate事件,监听浏览器历史的变化
- createRouteMap:初始化routeMap属性,把构造函数中传入的路由规则转换成键值对的形式存储到routeMap里面
- initComponent: 创建router-link和router-view这两个组件
实现思路
- 导出一个VueRouter的类,在类里面定义一个install静态方法
- 判断当前插件是否已经被安装
- 把VUE的构造函数记录到全局变量中
- 把创建Vue实例时传递的Router对象注入到所有的vue实例上
let _Vue = null
export default class VueRouter {
static install (Vue) {
// 判断当前插件是否已经被安装
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
/*
*把VUE的构造函数记录到全局变量中,当前的install方法是一个静态方法,
*在这个静态方法中我们接收了一个参数Vue的构造函数,而将来我们在Vue-router中的一些实例方法中还要使用Vue的构造函数
*/
_Vue = Vue
// 把创建Vue实例时传递的Router对象注入到所有的vue实例上
// 混入,
_Vue.mixin({
beforeCreate() {
if (this.$options.router) { // 实例的选项才有router,组件的选项没有router
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
},
})
}
}
- 创建constructor构造函数
constructor (options) {
this.options = options // 记录构造函数中传入的选项
this.routeMap = {} // 把options中传入的路由规则解析出来,存储到routeMap, 键:储存的是路由地址,值:存储的是路由组件;
this.data = _Vue.observable({ // observable: 创建响应式的对象
current: window.location.pathname // 存储当前路由地址
})
}
- createRouteMap() 遍历所有路由规则
createRouteMap () {
// 遍历所有的路由规则,把路由规则解析成键值对的形式,存储到routeMap对象中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
- 创建initComponent创建组件
initComponent (Vue) {
Vue.component('router-link', {
props: {
to: String
},
// 使用运行时版本的
render (h) {// h的作用是帮我们创建虚拟dom
// 第一个参数:选择器,第二个参数:属性, 事件,第三个参数:子元素
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
}, [this.$slots.default])
},
methods: {
clickHandler (e) {
history.pushState({}, '', this.to)
this.$router.data.current = this.to
e.preventDefault()
}
}
// 需要带编译器版本的
// template: ' '
})
const self = this
Vue.component('router-view', {
render (h) {
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
- 创建initEvent方法用来注册popstate事件
initEvent () {
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}
- init方法调用
init () {
this.createRouteMap()
this.initComponent(_Vue)
this.initEvent()
}
完整代码
let _Vue = null
export default class VueRouter {
// Vue.use()中调用install方法的时候会传递两个参数, 一个是VUE的构造函数,第二个参数是可选的选项对象
/**
* 判断当前插件是否已经被安装,如果已经安装了就不需要重复安装
* 把VUE的构造函数记录到全局变量中来,当前的install方法是一个静态方法,在这个静态方法中我们接收了一个参数Vue的构造函数,而将来我们在Vue-router中的一些实例方法中还要使用Vue的构造函数
* 把创建Vue实例时传递的Router对象注入到所有的vue实例上
*/
static install (Vue) {
// 判断当前插件是否已经被安装
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
// 把VUE的构造函数记录到全局变量中
_Vue = Vue
// 把创建Vue实例时传递的Router对象注入到所有的vue实例上
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate() {
if (this.$options.router) { // 实例的选项才有router,组件的选项没有router
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
},
})
}
constructor (options) {
this.options = options // 记录构造函数中传入的选项
this.routeMap = {} // 把options中传入的路由规则解析出来,存储到routeMap, 键:储存的是路由地址,值:存储的是路由组件;
this.data = _Vue.observable({
current: window.location.pathname // 存储当前路由地址
})
}
init () {
this.createRouteMap()
this.initComponent(_Vue)
this.initEvent()
}
// createRouteMap()的作用是把我们构造函数中传过来的选项中的路由规则,存储到routeMap对象中
createRouteMap () {
// 遍历所有的路由规则,把路由规则解析成键值对的形式,存储到routeMap对象中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
// initComponents
initComponent (Vue) {
Vue.component('router-link', {
props: {
to: String
},
render (h) {// h的作用是帮我们创建虚拟dom
// 第一个参数:选择器,第二个参数:属性, 事件,第三个参数:子元素
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
}, [this.$slots.default])
},
methods: {
clickHandler (e) {
history.pushState({}, '', this.to)
this.$router.data.current = this.to
e.preventDefault()
}
}
// template: ' '
})
const self = this
Vue.component('router-view', {
render (h) {
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
initEvent () {
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}
/**
* 总结:
* 完整版本的vue包含运行时和编译器,完整版本的vue里面的编译器就是把我们的模版帮我们编辑成render函数
* 运行时版本的vue在组件中我们要自己来写render函数,不支持template
*/
}
最后把router配置文件的vue-router改成自己定义的文件
import VueRouter from '../vuerouter/index'
注意
- vue-cli创建的项目默认使用的是运行时版本的Vue.js,如果想切换成编译器版本的Vue.js,需要修改vue-cli配置
项目的根目录创建vue.config.js文件,添加下面的代码
module.exports = { runtimeCompiler: true }