手写简版vue-router插件

248 阅读2分钟

可以思考下平常在使用vue-router时经历了那几个步骤?

  • 步骤一:使用vue-router插件 router.js
    import Router from 'vue-router'
    Vue.use(Router)
  • 步骤二:创建Router实列,router.js
    export default new Router({...})
  • 步骤三:在更组件上添加改实例 main.js
    import router from './router'
    new Vue({
        router,
    }).mount("#app");
  • 步骤四:添加路由视图,App.vue
    <router-view></router-view>
  • 导航
    <router-link to='/'>Home</router-link>
    <router-link to='/about'>About</router-link>

思考问题:

    为什么Router要use一下?它里面做了什么事情?
		
    创建的router实例,为什么要添加到更组件里面?目的是什么?

    <router-view> 为什么可以直接使用,他的作用的什么?

    <router-link> 为什么可以使用它做导航?它的导航到底是怎么实现的?

vue-router 的源码实现

  • 单页面应用程序中 url发生变化时候,不能刷新,显示对应的视图内容

需求分析

  • spa页面不能刷新
    hash #/about
    History api/about
  • 根据url显示对应的内容
    router-view
    数据响应式:current变量持有url地址,一旦变化,动态重新执行render

任务

  • 实现一个插件
    实现VueRouter类
        1.处理路由选项
        2.监控url变化,hashChange
        3.响应这个变化
    实现install方法
        1.$router注册   (将来能在任何一个组件里使用$router.push()实现路由跳转 等。。。。)
        2.两个全局组件(router-view  router-link)
        

代码实现

  • vue-router.js
/* 
    vue 插件的编写
    实现一个install方法
*/
let Vue;
class VueRouter {
    constructor(options){
        // console.log(Vue);
        this.$options = options 
        // console.log(options);

        // 保存当前hash到current
        // current 必须是响应式的,
        // Vue.set(this,current,'/')  不行,set要求传入的对象为响应式对象
        // Object.defineProperty()  不行,defineProperty 只是拦截,并没有建立依赖关系
        // 方法一 让vue替我们去做current 的响应式工作,但是这样比较繁琐
        /* new Vue({
            data(){
                return{
                    current:'/'
                }
            }
        }) */
        // 方法二 vue隐藏API  作用 :给指定对象定义一个响应式属性
        Vue.util.defineReactive(this,'current',window.location.hash.slice(1)||'/')
        // this.current = '/'
        // 监控hashChange
        window.addEventListener('hashchange',()=>{
            // #/about => /about
            this.current = window.location.hash.slice(1)
        })
    }
}

// 形参1是vue的构造函数 install.call(VueRouter,Vue)  方便在插件内部对Vue做一些深加工处理    目的是:便于扩展
VueRouter.install = function (_Vue) {
    Vue = _Vue
    
    // 1. 将$router注册一下
    // 如果直接注册会访问不到vue实列,因为install方法在use的时候就执行了
    // Vue.$router = 
    // 所有必须将$router注册推迟到将来的某一时刻:跟实列创建前
    Vue.mixin({
        beforeCreate(){
            // 只需要跟实列时注册一次
            if (this.$options.router) {
                Vue.prototype.$router = this.$options.router
            }
        }
    })
    

    //2. 注册两个全局组件,router-view router-link
    Vue.component('router-link',{
        props:{
            to:String,
            require:true
        },
        // template:"<div>touter-link</div>"
        // h就说createdElement() 作用返回一个虚拟DOM
        // <router-link to="/about">asdf</router-link>
        // 获取插槽内容:this.$slots.default
        render(h){
            // console.log(this.$slots.default);
            // console.log(h);
            return h('a',{
                attrs:{
                    href:'#'+this.to//值未组件上传进来的to属性的值
                }//a标签的特性
            },this.$slots.default)
        }
    })
    Vue.component('router-view',{
        // template:"<a>touter-view</a>"
        render(h){
            // 可以·传入一个组件直接渲染
            // 思路:可以根据url的hash部分动态匹配这个渲染的组件。从而实现动态渲染
            // window.location.hash 与路由映射表(routers)里面的pash进行对比
            // console.log(this.$router.$options.routes);
            // console.log(this.$router.current);
            const router = this.$router.$options.routes.find(router=>router.path===this.$router.current)
            let {component} = router
            return h(component)
        }
    })
   
}

export default VueRouter