汪图南
  • RAG

    • RAG
  • 快速入门
  • 高级技巧
前端面试之道
  • 打包工具

    • Webpack
    • Rollup
  • TypeScript

    • TypeScript基础
    • TypeScript类型挑战
  • CSS预编译器

    • SASS
  • 自动化测试

    • Vue应用测试
  • Vue2.0源码分析
  • Vue3.0源码分析
  • 数据结构和算法(基础)
  • LeetCode(刷题)
  • JavaScript书籍

    • 你不知道的JavaScript(上)
    • 你不知道的JavaScript(中下)
    • JavaScript数据结构和算法
    • JavaScript设计模式与开发实践
    • 深入理解ES6
  • Git书籍

    • 精通Git
Github
  • RAG

    • RAG
  • 快速入门
  • 高级技巧
前端面试之道
  • 打包工具

    • Webpack
    • Rollup
  • TypeScript

    • TypeScript基础
    • TypeScript类型挑战
  • CSS预编译器

    • SASS
  • 自动化测试

    • Vue应用测试
  • Vue2.0源码分析
  • Vue3.0源码分析
  • 数据结构和算法(基础)
  • LeetCode(刷题)
  • JavaScript书籍

    • 你不知道的JavaScript(上)
    • 你不知道的JavaScript(中下)
    • JavaScript数据结构和算法
    • JavaScript设计模式与开发实践
    • 深入理解ES6
  • Git书籍

    • 精通Git
Github
  • 介绍

    • 介绍和参考
  • 源码目录设计和架构设计

    • 设计
  • Rollup构建版本

    • Rollup基础知识
    • Vue中的Rollup构建
  • 从入口到构造函数整体流程

    • 整体流程
    • initGlobalAPI流程
    • initMixin流程
    • stateMixin流程
    • eventsMixin流程
    • lifecycleMixin流程
    • renderMixin流程
  • 响应式原理

    • 介绍
    • 前置核心概念
    • props处理
    • methods处理
    • data处理
    • computed处理
    • watch处理
    • 深入响应式原理
    • 依赖收集
    • 派发更新
    • nextTick实现原理
    • 变化侦测注意事项
    • 变化侦测API实现
  • 虚拟DOM和VNode

    • 虚拟DOM
    • VNode介绍
    • Diff算法
  • 组件化

    • 介绍
    • $mount方法
    • render和renderProxy
    • createElement
    • createComponent
    • 合并策略
    • update和patch
    • 组件生命周期
    • 组件注册
  • 编译原理

    • 介绍
    • compileToFunctions
    • parse模板解析
    • optimize优化
    • codegen代码生成
  • 扩展

    • 扩展
    • directive指令
    • filter过滤器
    • event事件处理
    • v-model
    • 插槽
    • Keep-Alive
    • Transition
    • Transition-Group
    • Vue.use插件机制
  • Vue-Router

    • 介绍
    • 路由安装
    • matcher介绍
    • 路由切换
    • 内置组件
    • 路由hooks钩子函数
  • Vuex

    • 介绍
    • Vuex安装
    • Vuex初始化
    • Vuex辅助API
    • Store实例API

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类上的commit和dispatch方法:

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类上的commit和dispatch方法为原始方法,在实例化store的时候,它根据当前module是否为子模块进行了特殊的处理,这段代码为:

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

我们可以看到,在上面这段代码中,它调用了makeLocalContext()方法,并它此方法的返回值赋值给了module.context,其中module.context存储的是子模块中的State、Getters、Mutations以及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,重写的commit和dispatch做了最重要的两件事情:异常类型判断、处理命名空间。

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

// 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)
  }
}

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

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构造函数。

最后更新时间: 2025/5/6 15:36
贡献者: wangtunan
Prev
Vuex辅助API