ES5实现一个 Vuex(1)

471 阅读2分钟

本前提是已经熟练使用vuex

1. 将 Vuex 挂载到window下面

(function (global, factory) {      
    global.Vuex = factory()
})(this, (function() {}))

2.给每一个vue组件挂载一个 store,取值时候可以通过store, 取值时候可以 通过 store.state取到

接下来我们看store是怎么混入到vue中的,首先就是 Vue.use()来安装这个插件,一般安装的插件都有个 install 方法,因为 Vue.use()也是调用者这个方法,并把Vue实例传入进来,然后通过 Vue.minxin()方法在 beforeCerete阶段将  store挂载到每个组件上,在递归每一个组件时候判断方法是,如果父级又store挂载到每个组件上,在递归每一个组件时候判断方法是,如果父级又store了就把父组件的$store赋值给子组件,最后return 出去

(function (global, factory) {      
    global.Vuex = factory()
})(this, (function() {
    var Vue // 存一份 Vue实例
    var install = function(_Vue) {
        Vue = _Vue
    }    Vue.mixin({      beforeCreate: function() {   
    // 深度优先,判断父组件是否有 $store,如果有 就把父级的 赋值给  组件的 $store        
        if( this.$options && this.$options.store ) {          
            this.$store = this.$options.store        
        }else{          
            this.$store = this.$parent && this.$parent.$store        
        }      
    }    
    })    
    return {     
        install: install    
    }
}))

3.那这个store应该如何写呢

store是通过这种方式去 创建的var store = new Vuex.Store({options}),所以我们需要写一个Store的构造函数, 并return出去

var Store = function( options ) {}return {
  install,
  Store
}

第一步实现 state,我们知道 state也是双向绑定的,所以想到了 Object.defineProperty,并写了一个只读的方法,

var Store = function( options ) {
    this._state = new Vue({  
        data: {        
            state: options.state      
        } })
    Object.defineProperty( this, 'state', {     
        enumerable: true,      
        configurable: true,      
        get() {        
            return this._state.state     
        },      
        set() {       
            {          
                throw new Error(("[vuex] " + '不能通过 state 直接赋值,可以通过  commit 触发'))       
         }      
        }    
    })
}
return {
  install,
  Store
}

第二步是要实现 getters ,这个相当于 vue的computed,将state处理后返回,首先封装一个forEach方法,之后收集所有的getters, 也是通过 Object.defineProperty,如果模板中用到了这个getter,直接执行getter函数并把state当参数传入

function forEach (obj, callback) {  
    Object.keys(obj).forEach(function(key) {    
        callback && callback(key, obj[key])  
    })
}
var store = this // 这里存储一下  this
var getters = options.getters || {}
this.getters = {}
forEach(getters, function(key, value) {  
Object.defineProperty(this.getters, key, {    
    enumerable: true,    
    configurable: true,    
    get: function() {      
        return value( store.state )   
    }  }) }.bind(this))

第三步是实现一个 同步的提交  mutations ,提交mutation需要一个 commit方法,同getters,首先先把所有的mutation搜集订阅,然后通过commit方法区发布

var mutations = options.mutations || {}
this.mutations = {}
forEach(mutations, function(key, value) {  
    this.mutations[key] = function(payload) {    
        value(store.state, payload )  
    }}.bind(this))
// 同步提交
Store.prototype.commit = function(type, payload) {  
    this.mutations[type](payload)
}

第四部是要实现一个 actions 异步提交,需要一个dispatch方法,同样也是先收集 actions,然后写dispatch方法

var actions = options.actions || {}
this.actions = {}
forEach( actions, function(key, value) {  
    this.actions[key] = function(payload) {    
        value(store, payload)  
    }}.bind(this))
Store.prototype.dispatch = function(type, payload) {  
    this.actions[type](payload)
}

到这看上去已经很完美了,基本的主要功能都实现了,但是运行时候发现,如果在 dispatch的时候里面 放了 commit 就会报错,找不到 commit 触发的方法,因为 forEach运行的会后 他的this是window,所以我们得想办法改变this指向,所有我们现在原型方法上添加 commit 和 dispatch,这样执行commit的时候会先找到原型方法,不会执行实例方法,并且绑定commit ,执行的时候this 指向都是 store ,就可以找到  commit的

var commit = store.commit, dispatch = store.dispatch
this.commit = function(type, payload) {  
    commit.call(this, type, payload )
}.bind(this)
this.dispatch = function(type, payload) {  
    dispatch.call(this, type, payload)
}.bind(this)

下面是完整的代码

// 将 Vuex 挂载到window下面(function (global, factory) {  global.Vuex = factory()})(this, (function() {  // # 一、为 每一个组件挂载  $store    // ** 1. vuex 会挂载一个 vm.$store 全局属性, 取值的时候可以 通过 $store.state ...      // 接下来我们看store是怎么塞入到 vue中的   Vue.use(vuex) -- 用来写插件,Vue.use()用来安装这个插件      // ,第一个参数是  Vue的构造函数, 第二个是传入的options      // 如果传入对象 默认调用这个库的install方法  https://cn.vuejs.org/v2/guide/plugins.html      // https://github.com/vuejs/awesome-vue#components--libraries 丰富的插件社区    // ** 2. 先保留一份  Vue     // ** 3. 初始化store 的时候  new Vux.store() 所有  Vuex应该还有一个   store的构造函数,      //  而且我们想在所有组件中都可以访问到  $store, 就要给每一个组件都添加 $store属性 vue中提供了  一个方法 vue.mixin()      // 它将影响每一个之后创建的 Vue 实例, 通过这个方法我们可以给所有的组件都添加上一个  $store,       // 通过这个方法我们可以看出来,vue是先渲染父组件再渲染子组件, 深度优先  var Vuefunction forEach (obj, callback) {  Object.keys(obj).forEach(function(key) {    callback && callback(key, obj[key])  })}  var install = function(_Vue) {    Vue = _Vue    Vue.mixin({      beforeCreate: function() {        // 深度优先,判断父组件是否有 $store,如果有 就把父级的 赋值给  组件的 $store        if( this.$options && this.$options.store ) {          this.$store = this.$options.store        }else{          this.$store = this.$parent && this.$parent.$store        }      }    })  }  var Store = function( options ) {    // # 二、处理  storestate, 我们不能直接修改 ,所有我们用es5写一个  getset 方法封装 this._state    // 理论上给 this.$store.state[xxx]属性赋值是不合法的,但是真实可以成功,而且得响应式的    this._state = new Vue({      data: {        state: options.state      }    })    Object.defineProperty( this, 'state', {      enumerable: true,      configurable: true,      get() {        return this._state.state      },      set() {        {          throw new Error(("[vuex] " + '不能通过 state 直接赋值,可以通过  commit 触发'))        }      }    })    // # 三、处理  storegetters, (相当于 vue的 computed的属性)    var store = this // 这里存储一下  this    var getters = options.getters || {}    this.getters = {}    forEach(getters, function(key, value) {      Object.defineProperty(this.getters, key, {        enumerable: true,        configurable: true,        get: function() {          return value( store.state )        }      })    }.bind(this))    // # 四、处理  storemutations     var mutations = options.mutations || {}    this.mutations = {}    forEach(mutations, function(key, value) {      this.mutations[key] = function(payload) {        value(store.state, payload )      }    }.bind(this))    // # 五、处理 storeactions    var actions = options.actions || {}    this.actions = {}    forEach( actions, function(key, value) {      this.actions[key] = function(payload) {        value(store, payload)      }    }.bind(this))    console.log(this)    // actions commit 的时候  thiswindow ,所以会找不到 触发的 commit函数名, 解决办法就是 先定义一个commitdispatch    var commit = store.commit, dispatch = store.dispatch    this.commit = function(type, payload) {      commit.call(this, type, payload )    }.bind(this)    this.dispatch = function(type, payload) {      dispatch.call(this, type, payload)    }.bind(this)  }  // 同步提交  Store.prototype.commit = function(type, payload) {    this.mutations[type](payload)  }  // 异步提交  Store.prototype.dispatch = function(type, payload) {    this.actions[type](payload)  }  return {    install: install,    Store: Store  }}))

git 地址

https://github.com/zhidanwang19910111/lite-vuex.git

系列文章

ES5实现一个 Vuex(2)

juejin.im/editor/draf…