乐趣区

关于vue-router:简单实现VUERouter

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    // 获取标签插槽内容
                );
            }
        }
    });
退出移动版