vue-router原理浅析

本文将浅析vue-router的具体实现,并且实现一个简易的vue-router。

一.前置知识

如果你很了解vue的插件应该怎么写,可以跳过此部分。

1.Vue.use( plugin) 安装 Vue.js 插件。

Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或属性
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })

  // 3. 注入组件选项
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}

2.Vue.mixin( mixin )

全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。

Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

new Vue({
  myOption: 'hello!'
})
// => "hello!"

 

二.流程

1.使用一个current变量储存当前url

2.监听浏览器url变化,改变current

3.监听current,获取应该渲染的新组件

4.渲染新组件

下面细讲:

1.使用一个current变量储存当前url

class HistoryRoute{
	constructor(){
		this.current=null;
	}
}
class vueRouter{
    constructor(options){
		this.mode=options.mode||'hash';
		this.routes=options.routes;
		this.history=new HistoryRoute;
	}
}


export default vueRouter;

在上述代码中,我们写了两个类,current用于保存当前路径,vueRouter则是我们要export出去的对象,所以他可以接受到一个options,这个options是从main.js(如果是vue-cli3则是router/index.js)中,new vueRouter时传进来的。(如下图,红框部分)

我们在构造函数中,取出mode,如果没有则默认是hash模式。把路由表保存到routes中。

2.监听浏览器url变化,改变current

window.addEventListener('load',()=>{
    this.history.current=location.hash.slice(1);
})
window.addEventListener('hashchange',()=>{
    this.history.current=location.hash.slice(1);
})

通过对load的监听,初始化history中current的值,通过对hashchange的监听,改变current的值。

 

3.监听current,获取应该渲染的新组件

current如何监听?vue提供了一个defineReactive的公共方法:

// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
Vue.util = {
    //warn: warn,
    //extend: extend,
    //mergeOptions: mergeOptions,
    defineReactive: defineReactive$$1
};
/**
  * Define a reactive property on an Object.
*/
  function defineReactive$$1 (
    obj,
    key,
    val,
    customSetter,
    shallow
  ) {
    var dep = new Dep();

    var property = Object.getOwnPropertyDescriptor(obj, key);
    if (property && property.configurable === false) {
      return
    }

    // cater for pre-defined getter/setters
    var getter = property && property.get;
    var setter = property && property.set;
    if ((!getter || setter) && arguments.length === 2) {
      val = obj[key];
    }

    var childOb = !shallow && observe(val);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
        var value = getter ? getter.call(obj) : val;
        if (Dep.target) {
          dep.depend();
          if (childOb) {
            childOb.dep.depend();
            if (Array.isArray(value)) {
              dependArray(value);
            }
          }
        }
        return value
      },
      set: function reactiveSetter (newVal) {
        var value = getter ? getter.call(obj) : val;
        /* eslint-disable no-self-compare */
        if (newVal === value || (newVal !== newVal && value !== value)) {
          return
        }
        /* eslint-enable no-self-compare */
        if (customSetter) {
          customSetter();
        }
        // #7981: for accessor properties without setter
        if (getter && !setter) { return }
        if (setter) {
          setter.call(obj, newVal);
        } else {
          val = newVal;
        }
        childOb = !shallow && observe(newVal);
        dep.notify();
      }
    });
  }

有了这个方法,我们就可以把监听current了

vueRouter.install=function(vue){
	vue.mixin({
		beforeCreate(){
			if(this.$options&&this.$options.router){
				this._root=this;
				this._router=this.$options.router;
				vue.util.defineReactive(this,'current',this._router.history);
			}else{
				this._root=this.$parent._root;
			}
		}
	})
}

 

4.渲染新组件

我们能拿到当前hash了,也有一个router的路由可以对上了,如果我们把router改造成hash为对象的key的数组,这样取对应的组件的时候就容易多了

createMap(routes){
	return routes.reduce((memo,current)=>{
		memo[current.path]=current.component;
		return memo
	},{})
}

这样我们就可以通过memo[hash] 很快的取到对应的组件。

vue.component('router-view',{
	render(h){
		let current=this._self._root._router.history.current;
		let routesMap=this._self._root._router.routesMap;
		return h(routesMap[current])
	}
})

这样,我们就基本完工啦~

测试:

新建一个vue项目,在项目中新建一个my-router的文件夹,在文件夹中新建index.js,把VueRouter的引用指向新的文件

import VueRouter from '../myrouter'

在浏览器地址栏更改地址,成功跳转!

 

留个小问题:

请在myrouter/index.js中实现一个简单的<router-link>

答案:

vue.component('router-link',{
		props:{
			to: String,
		},
		render(h){
			var getChildrenTextContent = function (children) {
				return children.map(function (node) {
					return node.children
						? getChildrenTextContent(node.children)
						: node.text
				}).join('')
			}
			var headingId = getChildrenTextContent(this.$slots.default);
			return h(
				"div",
				[
					h('a', {
						attrs: {
							name: headingId,
							href: '#' + this.to
						}
					},this.$slots.default)
				]
			)
		}
		
	})

完整代码已经上传到文章附件中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值