简单实现VUE-Router

Posted 欧怼怼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单实现VUE-Router相关的知识,希望对你有一定的参考价值。

github

vue-router Vue-routerVue.js官方的路由管理器。

它和Vue.js的核心深度集成,让构建单页面应用变得易如反掌。

安装

vue add router

核心步骤

  • 步骤一:使用vue-router插件

    //router.js
    import Router from \'vue-router\';
  • VueRouter是一个插件
  • 1)实现并声明两个组件router-view router-link
  • 2)install: this.$router.push()
  • */
    Vue.use(Router); // 引入插件

  • 步骤二:创建Router实例

    // router.js
    export default new Router({...})   // 导出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>
    this.$router.push(\'/\');
    this.$router.push(\'/about\')

    vue-router简单实现

    需求分析

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

    • hash:#/about
    • History api:/about
  • 根据url显示对应的内容

    • router-view
    • 数据响应式:current变量持有url地址,一旦变化,动态执行render

    任务

  • 实现一个插件

    • 实现VueRouter
    • 处理路由选项
    • 监控url变化
    • 响应变化
    • 实现install方法
    • $router注册
    • 两个全局组件

    实现

    创建新的插件

    Vue2.x项目中的src路径下,复制一份router文件,重命名为ou-router
    然后在ou-router路径下新建一个ou-vue-router.js文件,并将index.js文件中的VueRouter引入改为ou-vue-router.js

    import VueRouter from \'./ou-vue-router\'

    同时将main.js中的router引入也修改一下。

    import router from \'./ou-router\'

    创建Vue插件

    关于Vue插件的创建:

  • 可以使用function实现,也可以使用objectclass实现;
  • 要求必须有一个install方法,将来会被Vue.use()使用

    let Vue;   // 保存Vue的构造函数,插件中需要用到
    class VueRouter {}
  • 插件:实现install方法,注册$router
  • 参数1是Vue.use()一定会传入
  • */
    VueRouter.install = function (_Vue) {

    Vue = _Vue;  // 引用构造函数,VueRouter中要使用

    }
    export default VueRouter;

    ### 挂载`$router`
    当我们发现`vue-router`引入`vue`的时候,第一次是在`router/index.js`中使用了`Vue.use(Router)`,在这个时候也就会调用了`vue-router`的`install`方法;而第二次则是在`main.js`中,创建根组件实例的时候引入`router`,即`new Vue({router}).$mount("#app")`。
    也就是说,当调用`vue-router`的`install`方法的时候,项目还没有创建`Vue`的根组件实例。因此我们需要在`vue-router`的`install`方法使用全局混入,延迟到`router`创建完毕才执行挂载`$router`。

    let Vue; // 保存Vue的构造函数,插件中需要用到
    class VueRouter {}
    /*

  • 插件:实现install方法,注册$router
  • 参数1是Vue.use()一定会传入
  • */
    VueRouter.install = function (_Vue) {

    Vue = _Vue;  // 引用构造函数,VueRouter中要使用
    /* 挂载$router */
    /*
    * 全局混入
    *   全局混入的目的是为了延迟下面逻辑到router创建完毕并且附加到选项上时才执行
    * */
    Vue.mixin({
        beforeCreate() {    // 此钩子在每个组件创建实例时都会调用
            /* this.$options即创建Vue实例的第一个参数 */
            if(this.$options.router){   // 只在根组件拥有router选项

    Vue.prototype.$router = this.$options.router; // vm.$router

            }
        }
    })

    }
    export default VueRouter;

    ### 注册全局组件`router-link`和`router-view`
    首先在`install`方法中注册两个全局变量。

    let Vue;
    class VueRouter {}
    VueRouter.install = function (_Vue) {

    Vue = _Vue;
    Vue.mixin({
        ...
    })
    /* 注册全局组件router-link和router-view */
    Vue.component(\'router-link\',{
        render(createElement){
            return createElement(\'a\',\'router-link\');     // 返回虚拟Dom
        }
    });
    Vue.component(\'router-view\',{
        render(createElement){
            return createElement(\'div\',\'router-view\');   // 返回虚拟Dom
        }
    })

    }
    export default VueRouter;

  • router-view是一个a标签
  • router-viewto属性设置到a标签的herf属性(先默认使用hash方法)
  • 获取router-view的插槽内容,插入a标签中

     Vue.component(\'router-link\', {
            props: {
                to: {
                    type: String,
                    required: true
                }
            },
            render(createElement) {      // 返回虚拟Dom
                return createElement(\'a\',
                    {
                        attrs: {href: \'#\' + this.to}    // 设置a标签的href属性
                    },
                    this.$slots.default    // 获取标签插槽内容
                );
            }
        });

    实现router-view

    router-view实质上根据url的变化,实时响应渲染对应的组件,而createElement函数是可以传入一个组件参数的。
    因此,我们不进行渲染任何内容,后面实现监听url变化后,从映射表获取到组件后,再来实现router-view

    Vue.component(\'router-view\', {
            render(createElement) {
                let component = null;
                return createElement(component);   // 返回虚拟Dom
            }
        })

    监听url变化

    我们在VueRouter类的constructor函数中监听url的变化,这里我们默认使用hash方式。
    而且,我们需要将存入url的变量设置为响应式数据,这样子当其发生变化的时候,router-viewrender函数才能够再次执行。

    class VueRouter {
        /*
        * options:
        *   mode: \'hash\'
        *   base: process.env.BASE_URL
        *   routes
        * */
        constructor(options) {
            this.$options = options;
            // 将current设置为响应式数据,即current变化时router-view的render函数能够再次执行
     const initial = window.location.hash.slice(1) || \'/\';
            Vue.util.defineReactive(this, \'current\',initial);
            // 监听hash变化
     window.addEventListener(\'hashchange\', () => {
                this.current = window.location.hash.slice(1);
            })
        }
    }

    因此,我们可以来实现router-view组件。
    render函数中,this.$router指向的是VueRouter创建的实例,因此我们可以通过this.$router.$option.routes获取路由映射表,this.$router.current获取当前路由,然后通过遍历匹配获取组件。

    Vue.component(\'router-view\', {
       render(createElement) {
              let component = null;
               // 获取当前路由对应的组件
     const route = this.$router.$options.routes
                 .find(route => route.path === this.$router.current);
            if (route) {
                    component = route.component;
            }
            return createElement(component);   // 返回虚拟Dom
           }
    })

    实现history模式

    前面的实现都默认为hash模式,接下来简单实现一下history模式。
    首先将监听url的代码优化一下,并判别mode的值来设置current的初始值,而history模式下初始值为window.location.pathname

    class VueRouter {
        /*
        * options:
        *   mode: \'hash\'
        *   base: process.env.BASE_URL
        *   routes
        * */
        constructor(options) {
            this.$options = options;
            switch (options.mode) {
                case \'hash\':
                    this.hashModeHandle();
                    break;
                case \'history\':
                    this.historyModeHandle();
            }
        }
        // Hash模式处理
     hashModeHandle() {
            // 将current设置为响应式数据,即current变化时router-view的render函数能够再次执行
     const initial = window.location.hash.slice(1) || \'/\';
            Vue.util.defineReactive(this, \'current\', initial);
            // 监听hash变化
     window.addEventListener(\'hashchange\', () => {
                this.current = window.location.hash.slice(1);
            })
        }
        // History模式处理
     historyModeHandle() {
            const initial = window.location.pathname || \'/\';
            Vue.util.defineReactive(this, \'current\', initial);
        }
    }

    然后我们来实现history模式下的router-link组件。
    history模式下,当我们点击router-link时,即点下a标签时,页面会重新刷新。所以我们需要设置一下其点击事件,取消默认事件,然后通过history.pushState去修改url,然后重设current的值。

    Vue.component(\'router-link\', {
        render(createElement) {      // 返回虚拟Dom
            const self = this;
            const route = this.$router.$options.routes
                .find(route => route.path === this.to);
            return createElement(\'a\',
                {
                    attrs: {href: this.to},    // 设置a标签的href属性
     on: {
                        click(e) {
                            e.preventDefault();   // 取消a标签的默认事件,即刷新页面
     history.pushState({}, route.name, self.to);   // 通过history.pushState来改变url
                            self.$router.current = self.to;
                        }
                    }
                },
                this.$slots.default    // 获取标签插槽内容
            );
        }
    })

    最后我们将两种模式的router-link组件进行一个合并。

    Vue.component(\'router-link\', {
        props: {
            to: {
                type: String,
                required: true
            }
        },
        render(createElement) {      // 返回虚拟Dom
            if(this.$router.$options.mode === \'hash\'){
                return createElement(\'a\',
                    {
                        attrs: {href: \'#\' + this.to}    // 设置a标签的href属性
                    },
                    this.$slots.default    // 获取标签插槽内容
                );
            }else{
                const self = this;
                const route = this.$router.$options.routes
                    .find(route => route.path === this.to);
                return createElement(\'a\',
                    {
                        attrs: {href: this.to},    // 设置a标签的href属性
     on: {
                            click(e) {
                                e.preventDefault();   // 取消a标签的默认事件,即刷新页面
     history.pushState({}, route.name, self.to);   // 通过history.pushState来改变url
                                self.$router.current = self.to;
                            }
                        }
                    },
                    this.$slots.default    // 获取标签插槽内容
                );
            }
        }
    });

以上是关于简单实现VUE-Router的主要内容,如果未能解决你的问题,请参考以下文章

代码片段 - Golang 实现简单的 Web 服务器

项目集成 vue-router 和 vuex

vue-router的简单实现原理

vue-router的简单介绍及应用

vue-router 2.0 常用基础知识点之router-link

vue3中的vue-router简单实现以及router变迁带来的思考