简易版Vue-Router实现
Vue-Router重要性不用说了。不想当一个只会使用的coder,尝试实现一个简易版的Vue-Router,来帮助自己加深理解掌握Vue-Router。Vue-Router有两种模式。默认模式hash,也就是会变更url中#后面的内容,不会刷新浏览器,另一种个是history模式,history模式一般配合服务端的返回来使用。本次实现history模式的Vue-Router(hash模式也是异曲同工),且适用于SPA,不依赖服务端的返回。
前置知识
插件、slot 插槽、混入、render 函数。
前置知识不清楚建议先阅读Vue官方文档,可以帮助更好理解。
Vue Router 的核心代码
// 注册插件
// Vue.use() 内部调用传入对象的 install 方法
Vue.use(VueRouter)
// 创建路由对象
const router = new VueRouter({
routes: [
{ name: 'home', path: '/', component: homeComponent }
]
})
// 创建 Vue 实例,注册 router 对象
new Vue({
router,
render: h => h(App)
}).$mount('#app')
实现思路
- 创建 VueRouter 插件,静态方法 install
- 判断插件是否已经被加载
- 当 Vue 加载的时候把传入的 router 对象挂载到 Vue 实例上(注意:只执行一次)
- 创建 VueRouter 类
- 初始化,options、routeMap、data (创建 Vue 实例作为响应式数据记录当前路 径)
- initRouteMap() 遍历所有路由信息,把组件和路由的映射记录到 routeMap 对象中
- 注册 popstate 事件,当路由地址发生变化,重新记录当前的路径
- 创建 router-link 和 router-view 组件
- 当路径改变的时候通过当前路径在 routerMap 对象中找到对应的组件,渲染 router-view
代码实现
let _Vue = null
export default class VueRouter {
// 接受两个参数,一个是Vue的构造函数
static install (Vue) {
if (VueRouter.install.installed) {
return
}
// 1. 判断当前插件是否安装
VueRouter.install.installed = true
// 2. Vue构造函数记录到全局,后续使用
_Vue = Vue
// 3. 把创建Vue实例时传入的router对象注入到Vue实例
// TODO 混入 所有Vue实例以及组件上都会被混入
_Vue.mixin({
beforeCreate () {
// 只有当前Vue实例上具有$options,才在Vue原型上挂载$router对象
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
// 进行初始化
this.$options.router.init()
}
}
})
}
// 构造函数
constructor (options) {
this.options = options
this.routeMap = {}
// observable 方法让一个对象可响应 Vue 内部会用它来处理 data 函数返回的对象
// 返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新
this.data = _Vue.observable({
current: '/'
})
}
// 初始化操作
init () {
this.createRouteMap()
this.initComponent(_Vue)
this.intiEvent()
}
// 将传递给Vue-Router对象的options对象转换成routeMap
createRouteMap () {
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
// 创建router-link
initComponent (Vue) {
Vue.component('router-link', {
props: {
to: String
},
methods: {
clickHandler (e) {
// pushState方法改变浏览器的地址栏,会被浏览器的历史记住 不刷新页面,不向服务器发送请求
window.history.pushState({}, '', this.to)
// router-link是Vue实例 都可以访问Vue.prototype 注册了$router对象
this.$router.data.current = this.to
// 阻止默认事件行为
e.preventDefault()
}
},
// Vue默认创构建的是运行时版本,不支持template,可以通过vue-cli配置或者使用render函数
// template: `<a :href="to"><slot></slot></a>`
render (h) {
// h 函数创建虚拟DOM 第一个参数标签名,第二个参数是属性对象, 第三个参数是内容
return h('a', {
attrs: {
'href': this.to
},
on: {
click: this.clickHandler
}
}, [this.$slots.default])
}
})
const self = this
// 创建router-view
Vue.component('router-view', {
render (h) {
// 获取路由组件
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
// 初始化事件处理
intiEvent () {
// popstate 事件函数 当浏览器地址栏发生变化时触发
window.addEventListener('popstate', () => {
// 改变当前的路径 data 是一个响应对象发生变化后重新加载组件
this.data.current = window.location.pathname
})
}
}
注意点
-
创建router-link和router-view组件时,没有使用template模板,因为vue-cli创建的项目默认使用的运行时版本的Vue。不支持template。解决方法如下
- 如果想切换成带编译器版本的 Vue.js 。项目根目录创建 vue.config.js 文件,添加 runtimeCompiler
module.exports = { runtimeCompiler: true }- 可以实现代码一样,不使用template,也不需要配置vue-cli,使用运行时版本支持的render函数对模板进行编译