实现一个简单的vue-router

239 阅读1分钟

vue-router原理

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'
window.app = new Vue({
    router,
    render: h => h(App)
}).$mount('#app')
// 步骤四:添加路由视图,App.vue
<router-view></router-view>
// 导航
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
// js
this.$router.push('/')
this.$router.push('/about')

vue-router源码实现

  • 单⻚⾯应⽤程序中,url发⽣变化时候,不能刷新,显示对应视图内容

需求分析

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

任务

  • 实现⼀个插件

    • 实现VueRouter类

      • 处理路由选项
      • 监控url变化,hashchange
      • 响应这个变化
    • 实现install⽅法

      • $router注册
      • 两个全局组件

实现⼀个插件:创建VueRouter类和install⽅法

  • 创建vue-router.js
let Vue; // 引⽤构造函数,VueRouter中要使⽤
// 保存选项
class VueRouter {
    constructor(options) {
        this.$options = options;
    }
}
// 插件:实现install⽅法,注册$router
VueRouter.install = function(_Vue) {
    // 引⽤构造函数,VueRouter中要使⽤
    Vue = _Vue;
    // 任务1:挂载$router
    Vue.mixin({
        beforeCreate() {
            // 只有根组件拥有router选项
            if (this.$options.router) {
                // vm.$router
                Vue.prototype.$router = this.$options.router;
            }
        }
    });
    // 为什么要⽤混⼊⽅式写?主要原因是use代码在前,Router实例创建在后,⽽install逻辑⼜需要⽤到该实例

    // 任务2:实现两个全局组件router-link和router-view
    Vue.component('router-link', Link)
    Vue.component('router-view', View)
};
export default VueRouter;

创建router-view和router-link

  • 创建router-link.js
export default {
    props: {
        to: String,
        required: true
    },
    render(h) {
        // return <a href={'#'+this.to}>{this.$slots.default}</a>;
        return h('a', {
            attrs: {
                href: '#' + this.to
            }
        }, [
            this.$slots.default
        ])
    }
}
  • 创建router-view.js
export default {
    render(h) {
        // 暂时先不渲染任何内容
        return h(null);
    }
}

监控url变化

  • 定义响应式的current属性,监听hashchange事件
class VueRouter {
    constructor(options) {
        // 定义响应式的属性current
        const initial = window.location.hash.slice(1) || '/'
        Vue.util.defineReactive(this, 'current', initial)
        // 监听hashchange事件
        window.addEventListener('hashchange', this.onHashChange.bind(this))
        window.addEventListener('load', this.onHashChange.bind(this))
    }

    onHashChange() {
        this.current = window.location.hash.slice(1)
    }
}
  • 动态获取对应组件,router-view.js
export default {
    render(h) {
        // 动态获取对应组件
        let component = null;
        const route = this.$router.$options.routes.find(route => route.path === this.$router.current)
        if (route) component = route.component
        return h(component);
    }
}

提前处理路由表

  • 提前处理路由表避免每次都循环
class VueRouter {
    constructor(options) {
        // 缓存path和route映射关系
        this.routeMap = {}
        this.$options.routes.forEach(route => {
            this.routeMap[route.path] = route
        });
    }
}
  • 使⽤,router-view.js
export default {
    render(h) {
        const {routeMap, current} = this.$router
        const component = routeMap[current] ? routeMap[current].component : null;
        return h(component);
    }
}