思路:
- init初始化的时候调用bindEvent()
- bindEvent()在window对象上绑定一个hashchange事件监听哈希值变化
- hashchange事件调用onHashChange(),其中的this用bind指向router实例
- onHashChange用来改变变量current(当前路径)的值
- createRouteMap用于把新的路径和对应的组件收集起来
- current在constructor内通过Vue实现响应式
- current的改变引起initComponent()中router-view的改变(拿到current路径在routerMap里对应的组件),通过render渲染新组件
自实现的源代码:
//保存传进来的Vue实例
let Vue;
//创建路由类
class VueRouter {
constructor(options) {
//保存配置对象
this.$options = options;
//创建路由path和route映射
this.routeMap = {};
//当前路径current需要响应式,利用vue响应式原理
this.app = new Vue({
data: {
current: '/' //根目录
}
})
}
init() {
//绑定浏览器事件
this.bindEvents();
//解析路由配置
this.createRouteMap(this.$options);
//创建router-link和router-view
this.initComponent();
}
bindEvents() {
//bind(this)是为了拿router实例
window.addEventListener('hashchange',this.onHashChange.bind(this))
window.addEventListener('load',this.onHashChange.bind(this))
}
onHashChange() {
//举个栗子,http://localhost/#/home,slice是为了去掉#,获取/home
this.app.current = window.location.hash.slice(1) || '/'
}
createRouteMap(options) {
options.routes.forEach(item => {
//['/home']:{path:'/home',name: 'home',component:Home}
this.routeMap[item.path] = item;
})
}
initComponent() {
//声明两个全局组件
Vue.component('router-link', {
props: {
to: String
},
render(h) {
//目标是<a :href="to">xxx</a>,href是原生的特性,所以不能用props,要用attrs
//this.$slots.defalut通过匿名插槽获取用户写的xxx
return h('a', {attrs:{href:'#' + this.to}},this.$slots.default)
}
})
Vue.component('router-view', {
//箭头函数指向外面class的VueRouter实例
render: (h) => {
const Comp = this.routeMap[this.app.current].component;
return h(Comp);
}
})
}
}
//把VueRouter变成插件
//参数_Vue来自于App.vue里的Vue.use,所以这个_Vue就是传进来的Vue实例
//用开头设置的变量Vue来保存,就不需要用import引入Vue了
VueRouter.install = function(_Vue) {
Vue = _Vue;
//混入任务,扩展vue
Vue.mixin({
beforeCreate() {
//这里的代码将来会在外面初始化的时候被调用,就是在new Vue的时候
//这样就实现了vue的扩展
//this是Vue组件实例,$options是new Vue({router,render: h => h(App)})里的参数
//但是这里只希望根组件执行一次
if(this.$options.router){
Vue.prototype.$router = this.$options.router;
this.$options.router.init();
}
}
})
}
export default VueRouter;
用自己的代码替换router.js中的import Router from 'vue-router'就可以了。