-
Vue Router 是 Vue.js (opens new window)官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:
-
面试中对于vue-router经常会被问到,那他背后的具体实现原理是什么呢? what?最后回答基本都是根据url栏变化,动态渲染对应的组件,可具体是怎么实现的呢,这个也是我最近学习的一个方向,这次简单的实现一个最基本的vue-router插件
-
首先,我们知道vue-router有两种模式,分别为history模式,hash模式,SPA单页面在url改变而页面并不刷新的原因就在于这两种模式,今天我们实现hash模式,history以后有机会再实现吧
-
安装
vue add vue-router 复制代码
我们可以直接使用vue官方脚手架的命令,安装,安装前会改变代码,记得保存哦
-
正常生成文件夹,其中主要使用到vue-router的文件为
其中index.js为router文件夹为我们生成的router配置项
而main.js为打包的入口
App.vue是对路由的应用,熟悉的味道,熟悉的配方…
好了,环境准备好了,那就就开始实现插件,刚才在index.js大家也发现了注释掉的my-router.js,主角开始登场.
-
在我们配置roputer时,根据Vue插件的机制,,在使用Vue.use(‘xxx’)的方法时,会调用插件的install方法,并将Vue的根实例传入,
我们先准备好基本的VueRouter类
//声明全局Vue let Vue // vue-router 类 class VueRouter{ constructor(options){ // 保存配置项,后期给mian.js中挂载的vue调用 this.options = options } } //实现install方法 VueRouter.install=(_Vue)=>{ Vue = _Vue } export default VueRouter 复制代码
-
基本的写法先解决掉,现在页面应该会报错,我们App.vue中使用,还没有注册定义,那么我们先正常注册两个组件
// <router-link to="/">Home</router-link> 常用的模式,本质其实是一个a标签,a标签对应的href属性加上'#' function createRouterLink() { return { props: { to: { type: String, required: true, }, }, // 因为我们执行的是未携带编译器的版本,所以无法解析template,需要使用render函数,使用插槽机制$slots.default渲其中内容 render(h) { return h( 'a', { attrs: { href: '#' + this.to, }, }, this.$slots.default ) }, } } 复制代码
-
现在解决 ,路由占位符,那它是怎么渲染其他的组件呢?很好解决,还是万能的render函数,Vue可以直接使用render(‘组件’)的方法,使动态渲染我们需要的那个组件,那么问题来了,我们怎么在install中拿到url对应的变化地址?又哪里得到组件呢?
//<router-view> function createRouterView(){ return { render(h){ /* 问题:1.从哪里拿到每次动态变化的url 2.从哪里得到对应的component */ h(null) //先使用null,让我们的程序不会报错 } } } 复制代码
-
其实可以从new VueRouter实例中得到路由表,先从问题1开始,我们知道windo对象的location中是可以直接拿到hash值的,在实例中就可以通过事件获取每次变化的url.
-
那先从VueRouter类开始
class VueRouter{ constructor(options){ this.options = options //获取初始值#后面的path部分 this.current = window.location.hash.slice(1) || '/' //监听hash事件,并每次重新赋值current window.addEventListener('hashchange',()=>{ this.current = window.location.hash.slice(1) }) } } 复制代码
-
问题2,在中mian.js中得到router的实例,并可以Vue的$options中,也可以获取到实例属性,这样我们就可以将这一系列代码通过Vue实例,串联起来(好抽象啊T_T),可是又要怎么才能把main.js的Vue实例与install方法串联起来,在什么时候做的?答案就是mixin,我们可以使用mixin在vue初始化的钩子函数中,混入我们的VueRouter实例
VueRouter.install = function(_Vue){ //全局定义的Vue Vue = _Vue Vue.mixin({ // 使用beforeCreate,是在初始化最早的钩子中混入 beforeCreate(){ //只在最开始的根实例中混入一次 if(this.$options.router){ //这样在每个组件中的$router中都可以拿到VueRouter的实例属性 Vue.prototype.$router = this.$options.router } } }) } 复制代码
-
然后我们就可以完成
//<router-view> function createRouterView(){ return { render(h){ const { current , options:{ routes } } = this.$router //取出path映射的component const currentComponent = routes.find(route => route.path === current).component h(currentComponent) } } } 复制代码
-
nice啊,有事没,没事点两下(意气风发)…啊嘞?emmmmm,怎么url改变了,不渲染对应的组件呢?Vue不是响应式的嘛,嗯?响应式,怎么把这个忘了,在Vue中数据改变对应的视图更新,可是我们的current不是响应式数据,Vue不会执行对应的update函数,那就把current改为响应式数据.怎么变呢?总不能再用Object.defineProperty()做吧,那太麻烦了.如果看过Vue的源码的话,你会知道Vue.util.defineReactive(target,key,val)这个方法,在源码中initState时observe绑定(以后有机会细说)…那我们开始改造代码
//还记得我们的全局Vue嘛,在下方呗install赋值后,开始它的作用了 let Vue class VueRouter{ constructor(options){ this.options = options this.current = window.location.hash.slice(1) || '/' window.addEventListener('hashchange',()=>{ this.current = window.location.hash.slice(1) }) //将current变为响应式数据 Vue.util.defineReactive(this,'current',this.current) } } 复制代码
-
最最基础的vue-router就实现了
-
-
这就是整体的一套流程梳理
-
总结:
-
Vue插件机制,需要实现install方法,install第一个参数为Vue的实例,需要全局保存
-
使用了组件中的render函数渲染组件,保证代码的正确编译
-
在VueRouter将url,options保存为自身的实例属性,通过main.js中拿到的VueRouter实例,并获取到router.js的配置,通过Vue.mixin方法在全局混入VueRouter的实例,在第一次初始化的时候混入到Vue根实例的原型中,以便各个组件中调用
-
使用Vue.util.defineReactive()方法,将url的hash值,变为响应式数据,使url改变时,触发对应的update函数,重新render
-
本系列为个人平时学习的一个记录,里面还有很多的不足与错误,希望技术好的大佬,可以指出我的错误,还有不足的地方,非常感激,每一个BUG都是我进步的阶梯,欢迎大家一起学习…后续我会持续更新各种系列,纪念第一次写文章的我…希望多年后的自己看到这里,还能还记得现在我对代码,对技术的热情…其中如果有侵权的行为,请联系我…谢谢