簡單實現VUE-Router

github

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

它和Vue.js的核心深度集成,讓構建單頁面應用變得易如反掌。javascript

安裝

vue add router

核心步驟

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

    //router.js
    import Router from 'vue-router';
  • VueRouter是一個插件
  • 1)實現並聲明兩個組件router-view router-link
  • 2)install: this.$router.push()
  • */
    Vue.use(Router); // 引入插件java

  • 步驟二:建立Router實例git

    // router.js
    export default new Router({...})   // 導出Router實例
  • 步驟三:在根組件添加該實例github

    // main.js
    import router from './router';
    new Vue({
        router   // 添加到配置項
    }).$mount("#app")
  • 步驟四:添加路由視圖vue-router

    <!--  App.vue  -->
    <router-view></router-view>
  • 步驟五:導航api

    <router-link to="/">Home</router-link>
    <router-link to="/about">About</router-link>
    this.$router.push('/');
    this.$router.push('/about')

    vue-router簡單實現

    需求分析

  • 單頁面應用程序中,url發生變化時候,不能刷新,顯示對應視圖app

    • 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    // 獲取標籤插槽內容
                );
            }
        }
    });
相關文章
相關標籤/搜索