学习vuex源码

这一篇主要是讲解vuex的大概实现,以及一些细节的说明。vue

vuex是如何实现的?

先从install方法看,安装插件的方法实现比较简单,调用applyMixin,最后执行的是这段逻辑Vue.mixin({ beforeCreate: vuexInit }),其实是在每一个组件建立时混入store实例。因此咱们能够在每一个组件上获取到store实例上的数据。react

if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
  options.init = options.init
    ? [vuexInit].concat(options.init)
    : vuexInit
  _init.call(this, options)
}
}

function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
  this.$store = typeof options.store === 'function'
    ? options.store()
    : options.store
} else if (options.parent && options.parent.$store) {
  this.$store = options.parent.$store
}
}
复制代码
function install (_Vue) {
  if (Vue && _Vue === Vue) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}
复制代码

接下来看看store这个类的代码:vuex

在这个类上暴露了commitdispatch方法,而且绑定了调用上下文为store的实例。dispatch支持异步更新数据是由于它内部的实现就是使用了promisepromise

// bind commit and dispatch to self
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {
  return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
  return commit.call(store, type, payload, options)
}
复制代码

下面两个方法是vuex的实现主要逻辑,installModule的具体逻辑不细说,从注释上咱们能够知道,这是一个初始化整个vuex配置生成对象的方法。根据配置进行递归执行。bash

// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)
复制代码

resetStoreVM方法比较重要,下面是该方法的详细代码:app

  1. vuex中的getter其实是使用vue计算属性实现的,Object.defineProperty里定义了getterget方法。
  2. store._vm实际就是vuexinstallModule生成的对象改形成响应式的数据,经过一个新的vue实例。
  3. 最后则是当咱们重置vuex的响应数据,须要销毁旧的实例,回收内存。
function resetStoreVM (store, state, hot) {
  const oldVm = store._vm

  // bind store public getters
  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    // direct inline function use will lead to closure preserving oldVm.
    // using partial to return function with only arguments preserved in closure enviroment.
    computed[key] = partial(fn, store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })

  // use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins
  const silent = Vue.config.silent
  Vue.config.silent = true
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}
复制代码

修改vuex的数据是如何响应到视图?

  1. vuexstategetter实际上就是对应的store._vmvue实例)中datacomputed,所以getter所依赖的state最后是经过watcher管理的。
  2. 当咱们在组件中使用vuexvuex生成的实例对象在vue实例化过程当中被改形成响应式的数据,当咱们有多个页面组件使用了vuex的数据,其实也是经过watcher管理,所以当咱们使用commitdispatch修改数据,最后触发了setter去通知全部订阅者(watcher)更新。

总结

vuex的设计其实并不复杂,简单的来说,就是一个对象,经过内部的方法管理内部的属性和读取内部的属性。异步

而实现的过程,则是经过一系列方法把咱们的配置生成一个root对象,而后利用vue实现内部数据的响应与依赖管理。而这里比较核心的部分则是当数据发生变化时,如何响应到对应的视图部分以及getter的依赖管理,这些逻辑的实现最后都是经过watcheride

相关文章
相关标签/搜索