如何手寫簡易的vue-router插件

咱們都知道vue單頁應用須要藉助vue-router插件來實現頁面的跳轉,但實現原理如何呢?下面簡單的分析下。單頁的跳轉通常有兩種方式:hash變化和history控制。下面以hash變化爲例說明:vue

1. 實現思路

  • 第一步,實現插件的install方法
  • 第二步:定義全局組件router-link和router-view,router-link用於路由跳轉,router-view用於匹配組件內容
  • 第三步:監控url的變化,好比監聽hashchange或popstate事件
  • 第四步:響應最新的url,藉助響應式屬性matched來實現,current爲路由變化的值,當current改變時,從新匹配路由對應關係到matched數組裏,而後觸發組件內容更新

2. 代碼實現

// rvue-router.js

let Vue;

class Router {
    constructor(options){
        this.$options = options;
        // this.current保存當前頁面的hash值
        this.current = window.location.hash.slice(1) || '/';
        // matched數組保存路由與組件的映射表,只保留當前路由和當前路由的嵌套路由
        Vue.util.defineReactive(this, 'matched', []);
        // match方法遞歸遍歷路由表,得到匹配關係的數組,也就是建立新的路由映射表
        this.match();

        // 3. 監控url的變化:監聽hashchange或popstate事件
        window.addEventListener('hashchange', this.onHashChange.bind(this));
        // 頁面刷新時獲取hash值
        window.addEventListener('load', this.onHashChange.bind(this));
    }

    onHashChange(){
        this.current = window.location.hash.slice(1);
        // hash變化時,路由映射表從新匹配
        this.matched = [];
        this.match();
    }

    match(routes){
        routes = routes || this.$options.routes;

        for(const route of routes){
            // 根路由
            if(route.path === '/' && this.current == '/'){
                this.matched.push(route);
                return;
            }

            // 嵌套路由 /main/about
            if(route.path !== '/' && this.current.indexOf(route.path) > -1){
                this.matched.push(route);
                if(route.children){
                    this.match(route.children);
                }
                return;
            }
        }
    }
}

// 1. 做爲一個插件:實現VueRouter的install方法
Router.install = function(_Vue){
    // 保存_Vue構造函數,在Router內部使用
    // 避免import Vue時致使打包文件過大,因此經過變量Vue來保存_Vue引用
    Vue = _Vue;
 
    // 掛載$router
    // (入口文件實例化Vue時的根組件)在組件根實例中掛載$router Vue.prototype.$router = router
    // 經過混入的方式,在每一個組件生命週期中實現掛載$router
    Vue.mixin({
        beforeCreate() {
            // 確保是根實例的時候才執行,只有根實例組件纔有router選項
            if(this.$options.router){
                Vue.prototype.$router = this.$options.router;
            }
        },
    });

    // 2. 實現兩個全局組件:router-view用於匹配組件內容,router-link用於跳轉
    Vue.component('router-link', {
        props:{
            to: {
                type: String,
                required: true
            }
        },
        render(h) {
            // <a href="#/form">連接</a>
            // <router-link to="/slot" /> 調用方式
            // h(tag, data, children)
            return h('a', {attrs: {href: `#${this.to}`}}, this.$slots.default);
        }
    });
    Vue.component('router-view', {
        render(h){
            // 標記當前router-view的深度,標記本身是router-view組件
            this.$vnode.data.routeView = true;
    
            let depth = 0;
            let parent = this.$parent;
            while(parent){
                const vnodeData = parent.$vnode && parent.$vnode.data;
                // 若是是router-view組件,則記錄routeView的深度
                if(vnodeData && vnodeData.routeView){
                    depth ++;
                }
                parent = parent.$parent;
            }
    
            // 4. 響應最新的url:建立一個響應式的屬性matched,當它改變時獲取對應組件的顯示內容
            // mathed保存的是組件路由的映射關係,只涉及當前路由和當前路由的子路由
            // this.$router指當前VueRouter的實例
            const { matched } = this.$router;
            const component = matched[depth] && matched[depth].component;
    
            return h(component);
        }
    });
}

export default Router
複製代碼

3. 使用方法

引入rvue-router插件後,先使用Vue.use(Router)註冊插件,調用rvue-router的install方法,而後建立router實例,將路由配置項做爲參數傳入。node

//router.js

import Vue from 'vue'
import Router from 'rvue-router'  // 引入上面的rvue-router.js

import Form from 'components_path/form'

const routes = [{
    path: '/form',
    name: 'form',
    component: Form
}]

// 第一步 應用註冊插件
Vue.use(Router);

// 第二步 建立實例
const router = new Router({
    mode: 'history',
    routes  //將路由配置項傳入
})

export default router

複製代碼

在入口文件main.js中引入路由配置router.jsvue-router

// main.js

import Vue from 'vue'
import App from './app'
import router from './router'

new Vue({
    render: h => h(App),
    router  // Vue.prototype.$router = router
}).$mount('#app')
複製代碼

npm地址:www.npmjs.com/package/rvu…npm

以上內容爲網上學習課程的複習總結。數組

相關文章
相關標籤/搜索