回顾核心代码,VueRouter 是 Vue 的一个插件,Vue.use()
用来注册一个组件,它接收一个函数或者对象,传入函数会直接调用函数,传入对象会调用对象的 install
方法。所以实现 VueRouter 要定义一个静态的install
方法。之后用 new 的方法创建了一个 VueRouter 实例,那么应该 VueRouter 应该是一个类或者构造函数。我们用类来实现,这个类接收一个对象作为构造参数。
//router/index.js
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
]
})
// main.js
new Vue({
// 3. 注册 router 对象,为 vue 实例注入:
// $route:存储了一些路由规则,包括当前的全路径,相对路径,组件名,传入参数
// $router: VueRouter 实例对象,提供一些路由相关的方法 比如常用的 push replace go,以及当前的路由模式mode(hash|history),当前路由的规则 currentRoute
router,
render: h => h(App)
}).$mount('#app')
VueRouter 的类图:
VueRouter
----------------
options
data
routeMap
Constructor(Options): VueRouter
install(Vue): void
init(): void
initEvent(): void
createRouteMap(): void
initComponents(Vue): void
实现:
使用 vue-cli
创建的 Vue 项目,Vue 默认是运行时版本,它不包含编译器,template
无法使用,需要通过配置vue.config.js
,将runtimeCompiler
设为 true,启用运行时编译器。如果不使用 template, 也可以选择手动实现组件的 render 函数。
History 模式
console.dir(Vue)
let _Vue = null
class VueRouter {
static install(Vue){
//1 判断当前插件是否被安装
if(VueRouter.install.installed){
return;
}
VueRouter.install.installed = true
//2 把Vue的构造函数记录在全局
_Vue = Vue
//3 把创建Vue的实例传入的router对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
_Vue.mixin({
beforeCreate(){
if(this.$options.router){ // 每个组件都是一个Vue实例,这里只需要给 new Vue() 挂载 $router 属性,所以要通过这个属性判断,因为组件的 $options 没有 router 属性。
_Vue.prototype.$router = this.$options.router
}
}
})
}
constructor(options){
this.options = options
this.routeMap = {}
// observable
this.data = _Vue.observable({ // 创建响应式对象
current:"/"
})
this.init()
}
init(){
this.createRouteMap()
this.initComponent(_Vue)
this.initEvent()
}
createRouteMap(){
//遍历所有的路由规则 吧路由规则解析成键值对的形式存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
initComponent(Vue){
Vue.component("router-link",{
props:{
to:String
},
render(h){
return h("a",{
attrs:{
href:this.to
},
on:{
click:this.clickhander
}
},[this.$slots.default])
},
methods:{
clickhander(e){
history.pushState({},"",this.to)
this.$router.data.current=this.to
e.preventDefault()
}
}
// template:"<a :href='to'><slot></slot><>"
})
const self = this
Vue.component("router-view",{
render(h){
// self.data.current
const cm=self.routeMap[self.data.current]
return h(cm)
}
})
}
// 实现浏览器前进后退按钮
initEvent(){
//
window.addEventListener("popstate",()=>{
this.data.current = window.location.pathname
})
}
}
Hash 模式
import Vue from 'vue'
console.dir(Vue)
let _Vue = null
export default class VueRouter {
// 实现 vue 的插件机制
static install(Vue) {
//1 判断当前插件是否被安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
//2 把Vue的构造函数记录在全局
_Vue = Vue
//3 把创建Vue的实例传入的router对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
// 初始化属性
constructor(options) {
this.options = options // options 记录构造函数传入的对象
this.routeMap = {} // routeMap 路由地址和组件的对应关系
// observable data 是一个响应式对象
this.data = _Vue.observable({
current: "/" // 当前路由地址
})
this.init()
}
// 调用 createRouteMap, initComponent, initEvent 三个方法
init() {
this.createRouteMap()
this.initComponent(_Vue)
this.initEvent()
}
// 用来初始化 routeMap 属性,路由规则转换为键值对
createRouteMap() {
//遍历所有的路由规则 把路由规则解析成键值对的形式存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
// 用来创建 router-link 和 router-view 组件
initComponent(Vue) {
// router-link 组件
Vue.component('router-link', {
props: {
to: String
},
// render --- 可在 vue 运行时版直接使用
render(h) {
// h(选择器(标签的名字), 属性,生成的某个标签的内容)
return h('a', {
attrs: {
href: '#' + this.to,
},
// 注册事件
// on: {
// click: this.clickHandler // 点击事件
// },
}, [this.$slots.default]) // this.$slot.default 默认插槽
},
});
// router-view 组件
const self = this; //这里的 this 指向 vueRouter 实例
Vue.component('router-view', {
render(h) {
// 根据 routerMap 中的对应关系,拿到当前路由地址所对应的组件
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
// 用来注册 hashchange 事件
initEvent () {
window.addEventListener('hashchange', () => {
this.data.current = this.getHash();
});
window.addEventListener('load', () => {
if (!window.location.hash) {
window.location.hash = '#/';
}
});
}
getHash() {
return window.location.hash.slice(1) || '/';
}
}