One Last You Jen Bird
  1. 1 One Last You Jen Bird
  2. 2 BREAK IN TO BREAK OUT Lyn
  3. 3 かかってこいよ NakamuraEmi
  4. 4 Warcry mpi
  5. 5 Life Will Change Lyn
  6. 6 Last Surprise Lyn
  7. 7 Flower Of Life 发热巫女
  8. 8 Time Bomb Veela
  9. 9 Hypocrite Nush
  10. 10 Libertus Chen-U
2018-05-11 00:44:21

Vuex源码解析——API原理

上篇文章总结了Vuex初始化过程中涉及的原理,接下来从API层面切入来分析Vuex的状态管理运作流程。

状态管理核心

commit

在Vuex中有一个核心规则在于所有的状态变更都需要由mutation驱动,而mutation的调用则是通过store实例的接口commit驱动的。源码如下:

commit (type, payload, options) {
  // check object-style commit
  if (isObject(type) && type.type) {
    options = payload
    payload = type
    type = type.type
  }
  const mutation = { type, payload }
  const entry = this._mutations[type]
  if (!entry) {
    console.error(`[vuex] unknown mutation type: ${type}`)
    return
  }
  this._withCommit(() => {
    entry.forEach(function commitIterator (handler) {
      handler(payload)
    })
  })
  if (!options || !options.silent) {
    this._subscribers.forEach(sub => sub(mutation, this.state))
  }
}

该方法通过对type的判断去调用结构化时registerMutation方法注册的相对应的handler,并通过_withCommit方法装饰commit状态,最后判断是否为静默模式。如果非静默模式则遍历订阅者的回调。

subscribe

该方法也是实例store暴露的状态管理API之一,由以上源码可知,其目的在于监听mutation并做出相关动作。

dispatch

mutation和commitAPI对应,action则对应于dispatch。源码如下:

 dispatch (type, payload) {
  // check object-style dispatch
   if (isObject(type) && type.type) {
     payload = type
     type = type.type
   }
   const entry = this._actions[type]
   if (!entry) {
     console.error(`[vuex] unknown action type: ${type}`)
     return
   }
   return entry.length > 1
     ? Promise.all(entry.map(handler => handler(payload)))
     : entry[0](payload)
 }

可以看到该方法的逻辑和commit接口很相似,唯一区别在于action的handler返回值为promise,在该函数出口需要适配promise。

watch

该方法主要用来注册监听getter变化的回调。其实现原理是利用了Vuex内部的vm实例的$watch方法。接受参数如下:

watch(fn: Function, callback: Function, options?: Object): Function

fn函数接受state为第一个参数,getter为第二个参数,需要有返回值。watch监听该返回值,options和vm.$watch的options相同。该方法会返回一个函数,调用即可停止监听。

registerModule

Vuex在创建时就会完成store的结构塑造,但有时候我们需要在初始化完成后再动态改变store的结构。该方法就是用来动态注册新的模块。源码如下:

registerModule (path, module) {
    if (typeof path === 'string') path = [path]
    assert(Array.isArray(path), `module path must be a string or an Array.`)
    this._runtimeModules[path.join('.')] = module
    installModule(this, this.state, path, module)
    // reset store to update getters...
    resetStoreVM(this, this.state)
  }

可以看到原理和之前讲解的初始化逻辑完全一样,通过重新调用installModule和resetStoreVM重置store。

unregisterModule

和动态注册模块对应的就是动态注销了,源码如下:

unregisterModule (path) {
    if (typeof path === 'string') path = [path]
    assert(Array.isArray(path), `module path must be a string or an Array.`)
    delete this._runtimeModules[path.join('.')]
    this._withCommit(() => {
      const parentState = getNestedState(this.state, path.slice(0, -1))
      Vue.delete(parentState, path[path.length - 1])
    })
    resetStore(this)
  }

首先删除运行时对应模块,再通过commit装饰把当前state从父state上删除,最后调用resetStore重置store状态。

hotUpdate

该方法用来配合webpack的热重载API同步实现store的热重载。源码如下:

hotUpdate (newOptions) {
  updateModule(this._options, newOptions)
  resetStore(this)
}

function updateModule (targetModule, newModule) {
  if (newModule.actions) {
    targetModule.actions = newModule.actions
  }
  if (newModule.mutations) {
    targetModule.mutations = newModule.mutations
  }
  if (newModule.getters) {
    targetModule.getters = newModule.getters
  }
  if (newModule.modules) {
    for (const key in newModule.modules) {
      if (!(targetModule.modules && targetModule.modules[key])) {
        console.warn(
          `[vuex] trying to add a new module '${key}' on hot reloading, ` +
          'manual reload is needed'
        )
        return
      }
      updateModule(targetModule.modules[key], newModule.modules[key])
    }
  }
}

可以看到其关键在于递归调用updateModule方法替换旧模块的actions,mutations和getters,再重置store。

辅助函数

Vuex提供了一系列语法糖方便操作store的各种属性。

mapState

该方法用于映射store中的state属性到组件的计算属性上。参数可以是对象或数组。源码如下:

export function mapState (states) {
  const res = {}
  normalizeMap(states).forEach(({ key, val }) => {
    res[key] = function mappedState () {
      return typeof val === 'function'
        ? val.call(this, this.$store.state, this.$store.getters)
        : this.$store.state[val]
    }
  })
  return res
}

function normalizeMap (map) {
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}

首先使用normalizeMap方法将参数统一转化为格式为{key, val}的对象数组,接着遍历该数组构造一个新对象,该对象的key和参数key对应,每个key的value是由val转化而来的新计算属性函数:如果val是函数则传入store的state和getters调用映射为返回值,否则映射为store.state.val。

mapGetters

该方法和mapState很相似,用于映射getters属性到组件的计算属性上。不同的是normalize后的val不能是函数只能是字符串,因为getters本身就是函数了。源码如下:

export function mapGetters (getters) {
  const res = {}
  normalizeMap(getters).forEach(({ key, val }) => {
    res[key] = function mappedGetter () {
      if (!(val in this.$store.getters)) {
        console.error(`[vuex] unknown getter: ${val}`)
      }
      return this.$store.getters[val]
    }
  })
  return res
}

mapActions

该方法用于映射dispatch方法到组件的methods属性上。实现方法也类似,区别在于映射的不是计算属性而是普通的对象。

export function mapActions (actions) {
  const res = {}
  normalizeMap(actions).forEach(({ key, val }) => {
    res[key] = function mappedAction (...args) {
      return this.$store.dispatch.apply(this.$store, [val].concat(args))
    }
  })
  return res
}

mapMutations

和mapActions完全一样,映射commit方法到methods属性上。

export function mapMutations (mutations) {
  const res = {}
  normalizeMap(mutations).forEach(({ key, val }) => {
    res[key] = function mappedMutation (...args) {
      return this.$store.commit.apply(this.$store, [val].concat(args))
    }
  })
  return res
}

小结

以上就是Vuex最常用的API了,可以看到并没有什么复杂的东西,主要还是一些辅助和驱动操作,核心部分都是在Vuex的store初始化部分就已经构建完成了。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「Vue」 标签。