vue-router实现(hash模式与history模式)

核心代码:

Main.js:

import Vue from 'vue'
import Router from 'vue-router'
//Vue.use可以传入对象和函数
//传入函数:直接调用这个函数
//传入对象:调用该对象的install方法
Vue.use(Router)

const router = new Router({    //配置路由规则
  routes:[    //配置路由
    
  ]
})
//创建vue实例并传入刚刚创建好的router对象
new Vue({
  router,
  render: h => h(App)
}).mount('#app')

Vue-router需要实现些什么?

vue-router实现(hash模式与history模式)_第1张图片

带+的是对外公布的方法,带_的是静态方法

有三个属性:

  • Options: 记录构造函数中传入的对象,即new VueRouter是传入的内容
  • Data: 一个对象,其中有一个current属性,记录当前路由地址,data对象是响应式的,实现路由变化实时更新页面,用Vue.observe()包裹的,使其称为响应式的
  • routeMap: 是一个对象,记录路由地址和组件的对应关系,将来把路由规则解析到routeMap中

方法:

  • Install: 静态方法,用来实现vue的插件机制, 使用Vue.use(VueRouter)时 会自动调用该方法
  • Constructor: 初始化属性
  • init: 调用下面的方法
  • initEvent: 注册popState事件,监听浏览器历史变化
  • createRouteMap: 初始化routeMap属性,将构造函数中传入的路由规则,转换成键值对的形式传入routeMap中
  • initComponent: 用来创建 router-view 和 router-link 组件

新建一个项目,实现我们自己的vue-router:

let _Vue = null

export default class VueRouter {
    static install(Vue){    //接收Vue构造函数作为参数
        //install方法需要做哪些事情?
        //1、判断当前插件是否被安装,如果已经被安装 就不需要在安装
        if(VueRouter.install.installed){
            return
        }
        // 新建一个变量控制VueRouter是否被安装
        VueRouter.install.installed = true
        //2、把vue的构造函数记录到全局变量中去,因为后面会用到Vue的构造函数
        _Vue = Vue
        //3、把创建Vue实例时传入的router对象注入到Vue实例上
        // 定义一个全局混入,全部组件都会执行这个混入
        _Vue.mixin({
            beforeCreate () {
                // Vue实例的$options属性上有router属性,组件没有,以此来判断是否为Vue实例,Vue实例才执行这个
                if(this.$options.router){
                    _Vue.prototype.$router = this.$options.router
                    // 初始化之后就调用init方法
                    this.$options.router.init()
                }
            }
        })
    }

    constructor (options) {   
        //需要接收一个对象,返回值是VueRouter对象
        // constructor构造函数中初始化options data routeMap属性
        // 记录构造函数中传入的options
        this.options = options
        //路由模式
        this.mode = options.mode || 'hash'
        // routeMap是一个对象,解析options中传入的routes对象,存储在键值对中 键:路由地址 值:路由组件
        // routerview会根据路由地址 在routerMap中找到对应的组件,将其渲染在浏览器中
        this.routeMap = {}
        // data是一个响应式的对象,当路由变化时 要去自动加载组件
        this.data = _Vue.observable({
            current: '/',     //存储当前路由地址,初始化为/
        })
    }
    createRouteMap () {
        //遍历路由规则,并将路由规则以键值对的方式 存储到routeMap中
        this.options.routes.forEach(route => {
            this.routeMap[route.path] = route.component
        })
    }
    init(){
        this.createRouteMap()
        this.initComponents(_Vue)
    }

    initComponents (Vue){
        Vue.component('router-link', {
            props: {
                to: String
            },
            template: ''
        })
    }
}

在项目的router/index.js中将VueRouter引用替换成我们写的,看下效果:

import VueRouter from '../VueRouter'

运行项目:发现一个没有见过的报错内容:

在这里插入图片描述

大致内容为:你正在使用运行时的vue,模板编译器不可用

原因是 我们在 initComponents方法中写了template,而运行时的版本不包含模板编译器,无法将其转换成render函数,我们需要了解下vue的构建版本

Vue的构建版本:

  • 运行时版: 默认不带编译器,不支持组件中的template选项,需要打包时提前编译(把template编译成render函数)

  • 完整版: 包含运行时版和编译器,体积比运行时版大10k左右,程序运行的时候吧模板转换成render函数

    编译器:运行时将template模板转换成render函数

Vue-cli创建的项目默认是用运行时版

那么有两个解决方案

一、

想要使用包含编译器的vue构建版本:需要配置vue.config.js

参考官方文档:

[https://cli.vuejs.org/zh/config/#runtimecompiler](

二、

在initComponents方法中不使用template, 使用render函数,改写一下

initComponents (Vue){
        Vue.component('router-link', {
            props: {
                to: String
            },
            // template: ''
            render (h) {    //h函数是vue传给我们的,接收三个参数  标签名  标签的属性  生成的标签里的内容(数组)
                return h('a', {
                    attrs: {
                        href: this.to
                    }
                }, [this.$slots.default])   //this.$slots.default获取默认插槽的内容
            }
        })
    }

完整代码:

let _Vue = null

export default class VueRouter {
    static install(Vue){    //接收Vue构造函数作为参数
        //install方法需要做哪些事情?
        //1、判断当前插件是否被安装,如果已经被安装 就不需要在安装
        if(VueRouter.install.installed){
            return
        }
        // 新建一个变量控制VueRouter是否被安装
        VueRouter.install.installed = true
        //2、把vue的构造函数记录到全局变量中去,因为后面会用到Vue的构造函数
        _Vue = Vue
        //3、把创建Vue实例时传入的router对象注入到Vue实例上
        // 定义一个全局混入,全部组件都会执行这个混入
        _Vue.mixin({
            beforeCreate () {
                // Vue实例的$options属性上有router属性,组件没有,以此来判断是否为Vue实例,Vue实例才执行这个
                if(this.$options.router){
                    _Vue.prototype.$router = this.$options.router
                    // 初始化之后就调用init方法
                    this.$options.router.init()
                }
            }
        })
    }

    constructor (options) {   
        //需要接收一个对象,返回值是VueRouter对象
        // constructor构造函数中初始化options data routeMap属性
        // 记录构造函数中传入的options
        this.options = options
        //确定路由模式
        this.mode = options.mode || 'hash'
        // routeMap是一个对象,解析options中传入的routes对象,存储在键值对中 键:路由地址 值:路由组件
        // routerview会根据路由地址 在routerMap中找到对应的组件,将其渲染在浏览器中
        this.routeMap = {}
        // data是一个响应式的对象,当路由变化时 要去自动加载组件
        this.data = _Vue.observable({
            current: '/',     //存储当前路由地址,初始化为/
        })
    }
    createRouteMap () {
        //遍历路由规则,并将路由规则以键值对的方式 存储到routeMap中
        this.options.routes.forEach(route => {
            this.routeMap[route.path] = route.component
        })
    }
    isHash(mode){
		return mode === 'hash'
	}
    init(){
        this.createRouteMap()
        this.initComponents(_Vue)
        this.initEvent()
    }

    initComponents (Vue){
    	 const self = this    //保存我们写的VueRouter中的this
        Vue.component('router-link', {    //生成router-link组件
            props: {
                to: String
            },
            // template: ''
            render (h) {
                return h('a', {
                    attrs: {
                        href:self.isHash(self.mode) ? '#' + this.to : this.to
                    }, 
                    on: {     //为生成的a标签注册事件
                        click:this.clickHandler
                    }
                }, [this.$slots.default])
            },
            methods: {
                clickHandler (e) {
                	if(self.isHash(self.mode)){
						this.$router.data.current = this.to
						//hash模式是基于锚点的切换,不需要阻止默认事件
					}else{
						//this代表当前router-link, 同样是一个vue实例
                    	history.pushState({}, '', this.to)
                    	this.$router.data.current = this.to   //改变data中的current属性
                    	e.preventDefault()   //阻止事件默认行为
					}
                    
                }
            }
        })
       
        Vue.component('router-view', {     //生成router-view组件
            render (h) {
                //self.data.current     //当前路由地址, 存在data.current中,且是响应式的
                const component = self.routeMap[self.data.current]
                return h(component)    //h函数能帮我们将template转换成 虚拟dom
                //此时 发现地址栏确实变化了,但是页面内容并没有去渲染,因为地址变化 就会去发送请求,而我们是单页面应用,找不到的
                // 因此,需要使用pushHistory方法, 地址栏变化,但不去发送请求,且将地址栏变化记录下来
                // 去修改router-link的默认行为
            }
        })
    }
    initEvent() {   //注册浏览器popstate事件||hashchange事件,监听浏览器地址栏变化
    	if(this.isHash(this.mode)){
            window.addEventListener('hashchange',() => {
            	//为data.current赋值
                let hash = window.location.hash.slice(1)
                this.data.current = hash
            })
        }else{
            window.addEventListener('popstate',() => {
            	//获取当前路由地址, 并为data中的current赋值
                this.data.current = window.location.pathname
            })
        }
    }
}

你可能感兴趣的:(vue-router,vue.js,javascript,前端)