回顾核心代码,VueRouter 是 Vue 的一个插件,Vue.use()用来注册一个组件,它接收一个函数或者对象,传入函数会直接调用函数,传入对象会调用对象的 install方法。所以实现 VueRouter 要定义一个静态的install方法。之后用 new 的方法创建了一个 VueRouter 实例,那么应该 VueRouter 应该是一个类或者构造函数。我们用类来实现,这个类接收一个对象作为构造参数。
//router/index.jsVue.use(VueRouter)const router = new VueRouter({routes: [{path: '/',name: 'Home',component: Home},]})// main.jsnew Vue({// 3. 注册 router 对象,为 vue 实例注入:// $route:存储了一些路由规则,包括当前的全路径,相对路径,组件名,传入参数// $router: VueRouter 实例对象,提供一些路由相关的方法 比如常用的 push replace go,以及当前的路由模式mode(hash|history),当前路由的规则 currentRouterouter,render: h => h(App)}).$mount('#app')
VueRouter 的类图:
VueRouter----------------optionsdatarouteMapConstructor(Options): VueRouterinstall(Vue): voidinit(): voidinitEvent(): voidcreateRouteMap(): voidinitComponents(Vue): void
实现:
使用 vue-cli创建的 Vue 项目,Vue 默认是运行时版本,它不包含编译器,template无法使用,需要通过配置vue.config.js,将runtimeCompiler设为 true,启用运行时编译器。如果不使用 template, 也可以选择手动实现组件的 render 函数。
History 模式
console.dir(Vue)let _Vue = nullclass 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 = optionsthis.routeMap = {}// observablethis.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.toe.preventDefault()}}// template:"<a :href='to'><slot></slot><>"})const self = thisVue.component("router-view",{render(h){// self.data.currentconst 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 = nullexport 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) || '/';}}
