Vuex原理

Vuex注入

Vuex是作为vue的一个插件而存在的,它只能应用在vue中,是因为其高度依赖vue中的computed依赖检测系统以及其插件系统。

vue中,每一个插件都需要一个公开的install方法,在vuex中,它的install方法中调用了applyMixin方法。这个方法的主要作用是在所有组件的beforeCreate生命周期中注入this.$store这样一个对象。源码如下:

// src/store.js
export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    if (__DEV__) {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}
// src/mixin.js
// 对应的applyMinix方法
export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    // vue版本大于2使用混入的方式,在每个组件的beforeCreate生命周期中混入vuexInit函数
    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)
    }
  }

  /**
   * Vuex init hook, injected into each instances init hooks list.
   */
  function vuexInit () {
    const options = this.$options
    // store injection
    if (options.store) {
      // 将参数中的store对象,赋值给被混入的组件的this.$store属性
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
  }
}

我们在使用vuex时的写法如下:

export default new Vuex.Store({
    state: {
        count: 0,
        name: 'index'
    },
    mutations: {
        
    },
    actions: {
       
    },
    modules: { moduleA, moduleB },
    getters: {
       
    }
})

Vuex 数据响应式原理

那么Vuex.Store具体是什么呢,我们来看看它的构造函数

// src/store.js
constructor (options = {}) {
    // Auto install if it is not done yet and `window` has `Vue`.
    // To allow users to avoid auto-installation in some cases,
    // this code should be placed here. See #731
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }

    if (__DEV__) {
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store, `store must be called with the new operator.`)
    }

    const {
      plugins = [],
      strict = false
    } = options

    // store internal state
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()
    this._makeLocalGettersCache = Object.create(null)

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

    // strict mode
    this.strict = strict

    const state = this._modules.root.state

    // 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)
    // 核心代码,vuex中的state与getter响应式的实现操作都在该函数中
    resetStoreVM(this, state)

    // apply plugins
    plugins.forEach(plugin => plugin(this))

    const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
    if (useDevtools) {
      devtoolPlugin(this)
    }
  }

除了一些初始化外,最重要的一行代码是resetStoreVM(this, state)

// src/store.js
function resetStoreVM (store, state, hot) {
  const oldVm = store._vm

  // bind store public getters
  store.getters = {}
  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  // 将getter收集入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 environment.
    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
  // 实例化一个vue实例,将该实例作为一个隐形组件处理,将state作为data中的值,getter作为计算属性。利用data和计算属性的响应式机制实现vuex数据的响应式
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
}

核心部分的代码意图其实就是将我们传入的state作为一个隐藏的vue组件的data。所以每次我们对其数据进行操作,本质上其实是修改这个组件的data。这里的computed则是vuex中的getter实际上也是走的computed机制。

总结

vuex注入流程大致如下

  • install(Vue) --> applyMixin(Vue)
  • applyMixin(Vue) --> Vue.mixin({beforeCreate: VuexInit})
  • VuexInit() 修改this指向到vue组件实例,将混入this.$store对象为传入的Store对象

vuex的响应原理如下

  • new Vuex.Store() --> resetStoreVM
  • resetStoreVM()中将store._vm指向一个初始化的vue实例,并将state放入data中。在该函数中还对wrappedGetters进行了收集,并将其存储在computed中,作为计算属性,传给初始化的这个实例

你可能感兴趣的:(Vue,javascript,vue.js,前端)