基于Vuex从零实现自己的Vuez插件-actions(四)

272 阅读4分钟

到目前为止,我们已经实现了vuex中的getters,mutations,接下来就该轮到我们的actions了。
在具体实现actions之前,我们必须明确actions的功能和用途.

详情可以参考vuex-actions

通过官网的介绍,我们总结得出,在actions中,我们主要做两件事,一件是commit mutaions,另一件是dispatch other actions

为了能够完成以上两件任务,我们必须使得在actions中能够访问到store中的commitdispatch方法。

将actions绑定到store

这步操作和getters/mutations中完全一样,有需要注意的地方在代码中给出了注释

import Vue from 'Vue'
class Store{
  constructor(options){
    this._vm = new Vue({
        data:options.state
    })
   ...//getters
   ...// mutations
   // actions
  let actions  = options.actions || {}
  this.actions = {}
  Object.keys(actions).forEach((key)=>{
      this.actions[key] = (payload) =>{
          actions[key](this,payload) // 在这里传入this使为了让我们能够访问到commit和dispatch方法
      }
  })
  get state(){
      return this._vm
  }
  // 在这里,我们实现一个dispatch方法
  dispatch(actionType,payload){
      this.actions[actionType](payload)
  }
  commit(mutationType,payload){
      this.mutations[mutationType](payload)
  }
}

接下来,让我们注册一个简单的actions,

const store = new Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment ({commit,dispatch}) {
      commit('increment')
    }
  },
  getters:{
      getCount(state){
          return state.count
      }
  }
})

现在我们注意一下,actions中的increment的执行流程

// options中的actions绑定到了store实例上
// actions['increment'](this) //执行
// {commit,dispatch} = this // 结构赋值
// commit('increment')
// this.mutations['increment'] 这里会出现问题,this此时并不是指`store`实例,而是指向undefined

this指向

类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。

class Logger {
  printName(name = 'there') {
    this.print(`Hello ${name}`);
  }

  print(text) {
    console.log(text);
  }
}

const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined

上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到print方法而报错。

解决this指向问题

一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。

class Logger {
  constructor() {
    this.printName = this.printName.bind(this);
  }

  // ...
}

另一种解决方法是使用箭头函数。

class Obj {
  constructor() {
    this.getThis = () => this;
  }
}

const myObj = new Obj();
myObj.getThis() === myObj // true

箭头函数内部的this总是指向定义时所在的对象。上面代码中,箭头函数位于构造函数内部,它的定义生效的时候,是在构造函数执行的时候。这时,箭头函数所在的运行环境,肯定是实例对象,所以this会总是指向实例对象。


因此我们必须想办法保障this是指向store实例的
在这里,我们将commit,dispatch声明为箭头函数,因为箭头函数中的this在创造时就被确定了,而不会随着上下文环境而发生变化。

  dispatch = (actionType,payload) =>{
      this.actions[actionType](payload)
  }
  commit = (mutationType,payload) =>{
      this.mutations[mutationType](payload)
  }

此时完整代码为

class Store {
  constructor(options) {
    this.data = options.state;
    let getters = options.getters || {}
    this.getters = {}
    // mutations
    let mutations = options.mutations || {}
    this.mutations = {}
    Object.keys(mutations).forEach((key) => {
      this.mutations[key] = (payload) => {
        mutations[key](this.state, payload)
      }
    })

    // 把getter对象上的属性全部绑定到this.getter上
    Object.keys(getters).forEach((key) => {
      Object.defineProperty(this.getters, key, {
        get: () => getters[key](this.state)
      })
    })

    let actions = options.actions || {}
    this.actions = {}
    Object.keys(actions).forEach((key) => {
      this.actions[key] = (payload) => {
        actions[key](this, payload) // 在这里传入this使为了让我们能够访问到commit和dispatch方法
      }
    })
  }
  get state() {
    return this.data
  }
  dispatch = (actionType, payload) => {
    this.actions[actionType](payload)
  }
  commit = (mutationType, payload) => {
    this.mutations[mutationType](payload)
  }
}

通过上面的代码,我们发现在实现getters,mutations,actions的绑定时,逻辑都是一样的,我们可以将其抽取出来,封装成一个函数。

let forEach  = (obj,callback)=>{
    Object.key(obj).forEach(key=>{
        callback(key,obj[key])
    })
}

使用封装函数,修改我们的代码:

let forEach  = (obj,callback)=>{
    Object.key(obj).forEach(key=>{
        callback(key,obj[key])
    })
}
class Store {
  constructor(options) {
    this.data = options.state;
    let getters = options.getters || {}
    this.getters = {}
    // mutations
    let mutations = options.mutations || {}
    this.mutations = {}
    forEach(getters,(key,fn)=>{
        Object.defineProperty(this.getter,key)=>{
            get:()=>{
                return fn(this.state)
            }
        }
    })
    forEach(mutations,(key,fn)=>{
        this.mutations[key] = (payload)=>{
            fn((this.state, payload))
        }
    })
    let actions = options.actions || {}
    this.actions = {}
    forEach(actions,(key,val)=>{
        this.actions[key] = (payload)=>{
            fn(this,payload)
        }
    })
  }
  get state() {
    return this.data
  }
  dispatch = (actionType, payload) => {
    this.actions[actionType](payload)
  }
  commit = (mutationType, payload) => {
    this.mutations[mutationType](payload)
  }
}

测试是否成功

store.dispatch('increment')
console.log(store.getters.getCount) // 输出1 符合预期

未完待续...