vue-router实现原理(简版)

307 阅读1分钟

Vue Router是Vue.js官方的路由管理器。它是Vue.js的核心深度集成,让构建单页面应用变得易如反掌。

使用

安装:vue add 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'
    new Vue({
    	router,
    }).$mount("#app");
  • 步骤四:添加路由视图
	<router-view></router-view>
  • 导航
	<router-link></router-link>

源码实现

** 作为一个插件存在:实现VueRouter类和install方法 **

** 实现两个全局组件:router-view用于显示匹配组件内容,router-link用于跳转 **

** 监控url变化:监听hashchange或popstate事件 **

** 响应最新url:创建一个响应式的属性current,当它改变时获取对应组件并展示 **

实现一个插件:创建VueRouter类和install方法

创建my-router.js

	let vue // 引用构造函数,VueRouter中要使用
    
    class VueRouter {
    	constructor(options) {
        	this.options = options
        }
    }
    
    //插件:实现install方法,注册$router
    VueRouter.install = function(_Vue) {
    	//引用构造函数,VueRouter中要使用
        Vue = _Vue;
        
        // 任务1:挂载$router
        Vuemixin({
        	beforeCreate() {
            	//只有根组件拥有router选项
                if(this.$options.router) {
                	// vm.$router
                    Vue.prototype.$router = this.$options.router
                }
            }
        });
        
        //任务2: 实现两个全局组件router-link和router-view
        Vue.component('router-link',Link);
        Vue.component('router-view',View)
    }
    
    export default VueRouter;

为什么要混入方式写?主要原因是use代码在前,Router实例创建在后,而install逻辑又需要用到该实例

创建router-view和router-link

创建my-link.js

	export default {
    	props: {
        	to: String,
            required: true
        },
        render(h) {
        	return h('a', {
              attrs: {
                  href: '#' _ this.to
              }
            },[
              this.$slots.default
            ])
        }
    }

创建my-router-view.js

	export default {
    	render (h) {
        	//暂时不渲染任何东西
            return h(null);
        }
    }

监听url变化

定义响应式的current属性,监听hashchange事件

	class VueRouter {
    	constructor(options) {
        	// current应该是响应的
            Vue.util.defineReactive(this, 'current', '/')
            
            //定义响应式的属性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)
        }
    }

动态获取对应组件,my-router-view.js

	export default {
    	render(h) {
        	// 动态获取对应组件
            let component = null;
            this.$router.$options.routes.forEach(route=> {
            	if(route.path === this.$router.current) {
                	component = route.component
                }
            });
            return h(component)
        }
    }

提前处理路由表

提前处理路由表避免每次都循环

	class VueRouter {
    	constructor(options) {
        	// 缓存path和route映射关系
            this.routeMap = {}
            this.$options.routes.forEach(route => {
           		this.routeMap[route.path] = route
            })
        }
    }

使用,krouter-view.js

	export default {
    	render(h) {
        	const { routeMap, current } = this.$router
            const component = routeMap[current] ? routeMap[current].component : null
            return h(component)
        }
    }