这里记录一下关于 vue-router 的学习,以及自己实现一个类似的东西
vue-router 分为 hash 模式和 history 模式,众所周知的是,hash 模式 是利用 监听 hashchange 事件来实现的,而 history 则是通过监听 popstate 事件 实现的,那么 到底是在什么时候实现监听的呢?
1、创建一个 VueRouter 类,并实现内部的 事件注册
let _Vue
export default class VueRouter {
// 使用静态方法,来让 vue 实现注册
static install(Vue) {
// 1、判断当前插件是否已经被安装
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
// 2、把Vue 构造函数记录到全局变量
_Vue = Vue
}
constructor(options) {
this.options = options
this.mode = options.mode || 'hash'
// 路由对象,格式为: {地址:组件}
this.routeMap = {}
// 将当前的 data 手动设置为 响应式的
this.data = _Vue.observable({
current: '/'
})
}
}
2、获取 vue 初始化时候注入的 router 对象
如下, router 是在vue 初始化的时候放到 vue 对象里的,那么怎么在 VueRouter 里面获得这个类呢?
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
// ======== router 是在 vue 初始化的时候进行设置的 ===========
router,
render: h => h(App),
}).$mount('#app')
可以在这里利用混入(mixin) 来获得 对应的 router 属性
static install(Vue) {
。。。
_Vue = Vue
// 3、把创建Vue 实例的时候传入 router 对象
// 混入。
_Vue.mixin({
// 只在Vue 的实例之中安装,不在组件内安装
// 这样每个组件都能有 这个样一个东西了
beforeCreate() {
if (this.$options.router) {
// 在初始化的时候,也就是 beforeCreate 生命周期获取 注入的 router 对象
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
// 判断当前是 hash 模式,还是 history 模式,
// 如果是 hash 模式的话,给初始化的 路径加上 #/
if (!window.location.hash && this.$options.router.mode === 'hash') {
history.replaceState({}, '', '#/')
}
}
}
})
}
3、初始化路由内部的属性和事件
init() {
// 初始化路由对象
this.initRouteMap()
// 初始化全局的 vue 组件
this.initComponents(_Vue)
// 初始化路由事件
this.initEvent()
}
①、初始化路由对象
initRouteMap() {
// 遍历所有的路由规则,然后放到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
②初始化vue 内部组件
这里的路由组件包括了 router-link 和 router-view ,关于 slot 属性不熟悉的,可以看一下 vue 中的 $slot 获取 插槽的节点
initComponents(Vue) {
let mode = this.mode === 'hash' ? '/#' : ''
Vue.component('router-link', {
props: {
to: String
},
// 使用了 vue 的 render 属性,也相当于是函数式组件
render(h) {
return h('a', {
attrs: {
href: this.to,
},
on: {
click: this.clickHandler
}
}, [this.$slots.default])
},
methods: {
// 阻止 a 标签的 默认事件,然后 使用 history 属性设置 路由
clickHandler(e) {
e.preventDefault()
history.pushState({}, '', mode + this.to)
// 注意的是,这里的 data.current 在前面已经被手动设置为 被监听的属性了
this.$router.data.current = this.to;
}
}
})
const _this = this
Vue.component('router-view', {
render(h) {
// 由于这里的 data.current 被手动监听了,所以这里的依赖会被收集起来,然后被改变的时候,动态改变这里的属性
const component = _this.routeMap[_this.data.current]
return h(component)
}
})
}
③监听浏览器事件
initEvent() {
// 监听路由变化的时候,把当前的路由赋值给 当前的值
// 比如点击 浏览器的前进和后退
if (this.mode === 'hash') { // 当为 hash模式的时候
window.addEventListener('hashchange', () => {
this.data.current = window.location.hash.substr(1)
})
} else { // 当为 history 模式的时候
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}
}
4、完整代码
let _Vue
export default class VueRouter {
static install(Vue) {
// 1、判断当前插件是否已经被安装
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
// 2、把Vue 构造函数记录到全局变量
_Vue = Vue
// 3、把创建Vue 实例的时候传入 router 对象
// 混入。
_Vue.mixin({
// 只在Vue 的实例之中安装,不在组件内安装
// 这样每个组件都能有 这个样一个东西了
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
if (!window.location.hash && this.$options.router.mode === 'hash') {
history.replaceState({}, '', '#/')
}
}
}
})
}
constructor(options) {
this.options = options
this.mode = options.mode || 'hash'
// 路由地址:组件
this.routeMap = {}
// 响应式的
this.data = _Vue.observable({
current: '/home1'
})
}
init() {
// 初始化路由对象
this.initRouteMap()
// 初始化全局的 vue 组件
this.initComponents(_Vue)
// 初始化路由事件
this.initEvent()
}
initRouteMap() {
// 遍历所有的路由规则,然后放到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
// 初始化 router-link
initComponents(Vue) {
let mode = this.mode === 'hash' ? '/#' : ''
Vue.component('router-link', {
props: {
to: String
},
render(h) {
return h('a', {
attrs: {
href: this.to,
},
on: {
click: this.clickHandler
}
}, [this.$slots.default])
},
methods: {
clickHandler(e) {
e.preventDefault()
history.pushState({}, '', mode + this.to)
this.$router.data.current = this.to;
}
}
// tempalet: '<a :href="to"><slot></slot></a>'
})
const _this = this
Vue.component('router-view', {
render(h) {
const component = _this.routeMap[_this.data.current]
return h(component)
}
})
}
initEvent() {
// 监听路由变化的时候,把当前的路由赋值给 当前的值
// 比如点击 浏览器的前进和后退
if (this.mode === 'hash') {
window.addEventListener('hashchange', () => {
this.data.current = window.location.hash.substr(1)
})
} else {
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}
}
}
5、使用
// router/index.js
import VueRouter from '../../vue-router/index'
import Vue from 'vue'
Vue.use(VueRouter)
const routes = [
{
path: '/home1',
component: () => import('../components/HelloWorld.vue')
},
{
path: '/home2',
component: () => import('../components/HelloWorld2.vue')
}
]
export default new VueRouter({
mode: 'hash',
routes
})
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')