Store实例API

Vuex中,它不仅提供了一些辅助API,还设计了一些好用的Store实例API来方便我们使用Vuex,这些API全部都在Store类的原型上。

commit 和 dispatch

对于主模块和子模块的commit或者dispatch而言,虽然它们使用形式上是相同的,但方法定义却它们稍有不同,我们以commit为例。

// 主模块commit
const store = {
  ...,
  actions: {
    increment ({ commit }, count) {
      commit('increment', count)
    }
  }
}

// 子模块
const A = {
  ...,
  actions: {
    increment ({ commit }, count) {
      commit('increment', count)
    }
  }
}

对于同一个commit而言,在主模块中我们不使用命名空间这很好理解,但是为什么在子模块中,我们也可以不使用命名空间?

在回答以上问题之前,我们先来看Store类上的commitdispatch方法:

export class Store {
  constructor (options = {}) {...},
  commit (_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
    // 省略异常情况代码
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    // 省略订阅代码
    // 省略异常情况代码
  }
  dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    // 省略异常情况代码
    // 省略订阅代码

    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)

    return new Promise((resolve, reject) => {
      result.then(res => {
        // 省略订阅代码
        resolve(res)
      }, error => {
        // 省略订阅代码
      })
    })
  }

我们称Store类上的commitdispatch方法为原始方法,在实例化store的时候,它根据当前module是否为子模块进行了特殊的处理,这段代码为:

const local = module.context = makeLocalContext(store, namespace, path)

我们可以看到,在上面这段代码中,它调用了makeLocalContext()方法,并它此方法的返回值赋值给了module.context,其中module.context存储的是子模块中的StateGettersMutations以及Actions

我们来看一下这个方法的实现代码:

function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''

  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        if (__DEV__ && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }

      return store.dispatch(type, payload)
    },

    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        if (__DEV__ && !store._mutations[type]) {
          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          return
        }
      }

      store.commit(type, payload, options)
    }
  }

  // getters and state object must be gotten lazily
  // because they will be changed by vm update
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}

我们可以看到,在这个方法中它根据是否有命名空间来决定是否选择重写commit或者dispatch,重写的commitdispatch做了最重要的两件事情:异常类型判断处理命名空间

如果要使用代码来表述以上逻辑的话,如下所示:

// A子模块commit和dispatch
commit('increment', 10)
dispatch('increment', 10)

const moduleALocal = {
  dispatch: (_type, _payload, _options) => {
    // 省略逻辑
    type = 'a/' + 'increment'
    return store.dispatch('a/increment', 10)
  },
  commit: (_type, _payload, _options) => {
    // 省略逻辑
    type = 'a/' + 'increment'
    store.commit(type, payload, options)
  }
}

registerModule

Vuex初始化完毕后,我们的module模块树已经构造好了,但有时候我们需要动态的去注册一个子模块,registerModule方法提供了这种能力,它的使用方式如下:

this.$store.registerModule('c', {
  namespaced: true,
  state: {
    count: 0
  },
  mutations: {
    increment (state, count) {
      state.count = count
    }
  },
  actions: {
    increment ({ commit }, count) {
      commit('increment', count)
    }
  },
  getters: {
    storeCount (state) {
      return state.count + 'C'
    }
  }
})

在注册完成后,我们打印store,其结果如下:

const store = {
  state: {
    count: 0,
    a: { count: 0 },
    b: { count: 0 },
    c: { count: 0 }
  },
  getters: {
    storeCount: 0,
    'a/storeCount': '0A',
    'b/storeCount': '0B',
    'c/storeCount': '0C'
  },
  _mutations: {
    'increment': function () {...},
    'a/increment': function () {...},
    'b/increment': function () {...},
    'c/increment': function () {...}
  },
  _actions: {
    'increment': function () {...},
    'a/increment': function () {...},
    'b/increment': function () {...},
    'c/increment': function () {...}
  }
}

接下来,我们来看一下registerModule方法的实现代码:

export class Store {
  constructor (options = {}) { ... },
  registerModule (path, rawModule, options = {}) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      assert(path.length > 0, 'cannot register the root module by using registerModule.')
    }

    this._modules.register(path, rawModule)
    installModule(this, this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    resetStoreVM(this, this.state)
  }
}

在这个方法中,registerinstallModuleresetStoreVM方法我们已经在之前介绍过了,这里不再赘述。

unregisterModule

有动态注册模块,就有动态卸载模块,它就是unregisterModule,其使用方式如下:

this.$store.unregisterModule('c')

当我们卸载A模块后,store实例上就没有了与A模块相关的属性或者方法了。

unregisterModule方法在Store类上的定义如下:

export class Store {
  constructor (options = {}) { ... },
  unregisterModule (path) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
    }

    this._modules.unregister(path)
    this._withCommit(() => {
      const parentState = getNestedState(this.state, path.slice(0, -1))
      Vue.delete(parentState, path[path.length - 1])
    })
    resetStore(this)
  }
}

在卸载子模块的方法中,它最重要的是调用unregister方法和resetStore,让我们先来看unregister方法。

unregister是定义在ModuleCollection类上的一个方法,其定义如下:

export default class ModuleCollection {
  constructor (rawRootModule) { ... },
  unregister (path) {
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]
    const child = parent.getChild(key)
    // 重要:实例化时安装的模块不能卸载,只能卸载动态安装的模块
    if (!child.runtime) {
      return
    }
    // 省略异常情况
    parent.removeChild(key)
  }
}

我们可以看到,unregister所做的事情就是从parent中移除自身,对我们的例子而言,它就是从主模块children中移除自身。

最后,我们来看一下resetStore方法的代码:

function resetStore (store, hot) {
  store._actions = Object.create(null)
  store._mutations = Object.create(null)
  store._wrappedGetters = Object.create(null)
  store._modulesNamespaceMap = Object.create(null)
  const state = store.state
  // init all modules
  installModule(store, state, [], store._modules.root, true)
  // reset vm
  resetStoreVM(store, state, hot)
}

可以看到resetStore方法就是把store实例上的相关属性全部清空,然后重新安装了一遍,此过程相当于重新执行了一遍Store构造函数。

最后更新时间:
贡献者: wangtunan