Vuex

vuex

基本概念

Vuex状态管理

状态管理模式

  • state,驱动应用的数据源
  • view,以声明方式将state映射到视图
  • actions,响应在view上的用户输入导致的状态变化

应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多少个视图依赖于同一状态
  • 来自不同视图的行为需要变更同一状态

Vuex

  • 一个全局的单例模式
  • 核心就是store,包含着应用中大部分的状态。

Vuex和单纯的全局对象的不同:

  • Vuex的状态存储是响应式的。Store中的状态发生变化时,相应的组件也会相应地得到高效更新。
  • 不能直接修改store中的状态。改变store中状态的唯一方式就是显示提交(commit)mutation。

Vuex是一个不闭合的状态管理模式实例,它将状态管理模式中的数据state和变更state的事物actions、mutations与Vue组件进行结合,形成一个闭合的状态管理。组件通过显示提交修改Vuex中数据,Vuex中数据结合Vue响应式原理来触发组件更新。其中,Devtools工具可以监听Vuex中mutations事务中对state的修改。

Store

  1. Store构造器选项

    • state – 存储数据,Store实例的根state对象

      1
      2
      3
      state: function () {
      return {}
      }

      传入的是一个函数,是为了防止state被共享,创建单例想法。与vue中data是一个函数想法一致。

    • getters – store实例的计算属性,类似于computed

      1
      2
      3
      4
      5
      getters: {
      getShoppingCartLen(state) {
      return state.shoppingCart.length
      }
      }
  • mutations – 修改store中状态的处理事务,类似事件注册

    1
    2
    3
    4
    5
    6
    7
    // 注册事务
    import {SET_USER_NAME} from 'mutations-types'
    mutations: {
    SET_USER_NAME: function (state, payload) {
    state.user.name = payload
    }
    }
    1
    2
    // 提交修改store中状态
    this.$store.commit('SET_USER_NAME', 'zhaoshijuan')

    vuex的store是响应式的,因此需要mutation操作store中状态时注意以下几点:

    1. 提前在store中初始化好所有所需属性
    2. 需要添加新属性:Vue.set(obj, property, value); 或者 新对象替换老对象。
  • actions – 异步操作事务

    1
    2
    3
    4
    5
    actions: {
    setusername: function (context, payload) {
    context.commit('SET_USER_NAME', payload)
    }
    }

    actions与mutations区别:action提交的是mutation,mutation直接修改状态;action可以包含异步操作。

  • modules – 模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    modules: {
    user: {
    namespaced: true,
    state: {name: ''},
    getters: {},
    mutations: {},
    actions: {}
    }
    }

    所有有关状态的,拿到的都是局部状态,包含context.state指向的也是局部状态。context.rootState指向根状态,或者作为参数rootState传递。

  • plugins – 插件,方法数组,store是方法参数

  • strict – 是否是严格模式。严格模式下,任何mutation处理函数以外修改Vuex state都会报错。

  • devtools – 订阅到devtools插件

  1. Store实例属性

    • state – this.$store.state是store中状态
    • getters – this.$store.getters是store中getters
  1. Store实例方法

    • commit – 提交mutation

      1
      2
      3
      4
      5
      this.$store.commit('mutationType', mutationPayload, options);
      this.$store.commit({
      type: 'mutationType',
      mutationPayload: mutationPayload
      }, options)
    • dispatch – 分发action

      1
      2
      3
      4
      5
      this.$store.dispatch(type, payload, options);
      this.$store.dispathc({
      type: type,
      payload: payload
      }, options)
    • replaceState – 替换store的根状态

    • watch – 响应式监听fn返回值。watch(fn, callback, options)
    • subscribe – 订阅mutation,mutation完成后调用
    • subscribeAction – 订阅action,默认是在action调用之前触发。含有before,after指定订阅处理函数在action分发之前,之后调用
    • registerModule – 注册指定path的module
    • unregisterModule – 卸载指定path的module
    • hotUpdate – 热替换新的action和mutation

辅助函数

  • mapState – 在计算属性中混入store中状态
  • mapGetters – 计算属性中混入store中getters
  • mapMutations – 方法methods混入mutation事务
  • mapActions – 方法methods混入action事务
  • createNamespacedHelpers – 创建基于命名空间的辅助函数,是一个包含指定命名空间的mapState,mapGetters,mapMutations,mapActions对象

源码实现

Vuex安装注册

  • Vue.use(Vuex) => 会触发Vuex的install方法
  • Vuex中applyMixin方法:对于Vue版本进行判断,2.x版本中,Vue.mixin({beforeCreate: vuexInit})
  • vuexInit方法:将this.$options.store注入到this.$store上

Store实例化 [Vuex依赖于promise]

  • new ModuleCollection(options)

    • ModuleCollection.register(path, rawModule)
    • new Module(rawModule):每个module模块
    • 对于刚开始,path为空数组,会执行this.root = newModule
    • rawModule.modules也就是在new Vuex.Store时传递的modules数据,如果有值,就会递归运行如下[将modules建立成一个树状结构]

      1
      2
      3
      4
      // this指向ModuleCollection,path是一直贯穿的数据
      forEachValue(rawModule.modules, (rawChildModule, key) => {
      this.register(path.concat(key), rawChildModule, runtime);
      })
    • path已经不再是空数组,会执行以下代码

      1
      2
      3
      4
      5
      6
      7
      8
      // this指向ModuleCollection,建立父子关系
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)

      // parent是module实例,parent.addChild方法如下[this指向module]
      addChild(key, module) {
      this._children[key] = module
      }
  • installModule(this, state, [], _this.module.root)

    上面数据state就是_this.module.root.state。installModule(state, rootState, path, module, hot)

    对于mutations,actions,getters,_wrapperGetters等初始化。遍历每个module的actions,mutations,getters,child数据

  • 根据namespace设置_store._modulesNamespaceMap[namespace] = module。
  • 设置每个module.context = makeLocalContext,可以避免重名问题,commit提交mutation时,不加namespace也可以成功。
  • makeLocalContext返回的是一个local对象:dispatch,commit,getters。dispatch、commit根据传递的参数namespace + type形成新的type值,再调用store[methodName](type)。

    mutation,action对应的commit(namespace/mutationName),dispathc(namespace/actionName),_mutation和_action一层对象,key值是含有一层层namespace;state对应的state[namespace]对象形式一层一层往下查找。

  • forEachMutation 递归registerMutation
  • forEachAction 递归registerAction,每个action(除了返回值是Promise类型的)被执行后返回的是一个Promise.resolve(action函数执行后结果)实例化后的Promise实例
  • forEachGetter 递归registerGetter,每个getter会挂载在store._wrappedGetters上
  • forEachChild 递归执行installModule,传递的path是:全局path一直concat(key)
  • resetStoreVM(this, state)

        保证了Vuex的store中state存储数据是响应式,并且getters类似于Vue的computed,拥有缓存。

  • store.getters的每个getter做一个get拦截,拦截返回store._vm[key]:store._vm实例上对应的computed-key

  • store._vm是一个Vue实例,data数据$$state: state, computed其实就是传递的所有的getters

实现Vuex关键点

构造函数参数

  • state响应式,利用new Vue({data: {$$state: state}})实例化Vue,使得state拥有和Vue一样的响应式。
  • getters缓存计算,利用Vue实例的computed特性。
  • mutations注意模块化,执行上下文利用call
  • actions注意模块化,同时context传递的不是store本身,而是一个拥有相同dispatch,commit,getters,state,rootGetters,rootState数据的对象
  • modules 递归实现
    • 首先需要一个LocalContext
    • 递归给rootState添加state形成一个树形状态管理
    • 将getters直接挂载在store_wrappedGetters,Object.defineProperty(store.getters)get拦截返回vue实例compute,注册到store实例的getters属性
    • 将mutations挂载在store._mutation,并且属性名称就是namespace + ‘/‘ + key,属性值是一个数组
    • 将actions挂载在store._actions,属性名称是namspace + ‘/‘ + key,属性值是一个数组
    • modules递归挂载在store._modules.root._children,一层一层形成一个树状结构

实例属性

  • state 返回的是store实例上_vm._data.\$\$state,也就是Vue实例中data的$$state数据
  • getters registerGetter时,将每个module提取到this._wrappedGetters对象中,在对store实例getters每个属性进行拦截时,Object.defineProperty中get返回this._wrappedGetters对应getter

实例方法

  • dispatch
  • commit
  • subscribe 返回的是一个函数,运行后可以停止订阅。订阅mutation,在每个mutation完成后调用。
  • subscribeAction 返回的是一个函数,运行后停止订阅。订阅action,在每个action分发调用。【before,after指定action分发之前之后】
  • replaceState 替换整个store实例上的state数据
  • watch 实际是this._watcherVM.$watch。返回函数用于停止监听。
  • registerModule 注册模块。需要installModule,resetStoreVM
  • unregisterModule 卸载模块。删除模块数据,同时将state也Vue.delete。根据最新数据installModule和resetStoreVM