Vuex基本原理解析

下面将从Vuex的安装到实现数据存取的过程进行讲解。建议下载一套Vuex源码进行对比学习

安装过程

在Vuex初始化的过程中,首先会经过安装操作,通过vue.use方法进行install安装,安装之前会对当前环境进行判断是否已经安装过Vuex,以避免重复安装。


//store.js中安装方法



if (!Vue && typeof window !== 'undefined' && window.Vue) {

  install(window.Vue)

}

...

...



export 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)

}

最后调用applyMixin方法将vuexInit(Vuex初始化的钩子函数)方法(存在mixin.js中)混入Vue组件的beforeCreate钩子中。其中vuexInit方法中对传入的store进行处理,以保证所有组件都共用同一份store。那么此时我们会想到,Vuex就是为了解决组件之间的数据共享问题,通过这种方式,可以实现每个组件访问的都是同一个数据仓库(store),接着往下看。


  function vuexInit () {

    const options = this.$options

    // store injection

    if (options.store) {

      /*存在store其实代表的就是Root节点,直接执行store(function时)或者使用store(非function)*/

      this.$store = typeof options.store === 'function'

        ? options.store()

        : options.store

    } else if (options.parent && options.parent.$store) {

      /*子组件直接从父组件中获取$store,这样就保证了所有组件都公用了全局的同一份store*/

      this.$store = options.parent.$store

    }

  }

接下来在源码store.js中的constructor中,大部分逻辑是对定义一些下面需要用到的数组对象、是否为严格模式、以及定义用来实现watch的vue实例等,我们可以暂时不用管。主要是看下面两个函数:


    // init root module.

    // this also recursively registers all sub-modules

    // and collects all module getters inside this._wrappedGetters

    /*初始化根module,这也同时递归注册了所有子modle,收集所有module的getter到_wrappedGetters中去,this._modules.root代表根module才独有保存的Module对象*/

    installModule(this, state, [], this._modules.root)



    // initialize the store vm, which is responsible for the reactivity

    // (also registers _wrappedGetters as computed properties)

    /* 通过vm重设store,新建Vue对象使用Vue内部的响应式实现注册state以及computed */

    resetStoreVM(this, state)

    

第一种方法是安装模块,主要通过命名空间识别和创建局部上下文来管理和响应子模块。

第二种方法是注册state并绑定getter。.defineProperty实现每个getter的get操作,比如获取thisseproperty.$store.getters.storest时获得的是storest._vm.test方法,返回定义后的方法给computed属性,为实现Vuex响应式做准备。


    forEachValue(wrappedGetters, (fn, key) => {

        // use computed to leverage its lazy-caching mechanism

        computed[key] = () => fn(store)

        Object.defineProperty(store.getters, key, {

          get: () => store._vm[key],

          enumerable: true // for local getters

        })

    })

接下来这段代码,通过new一个Vue对象,借助Vue内部的响应式原理实现注册state以及computed,来实现Vuex的响应式操作。好了,那么文中一开始提到的第一个问题已经回答了!


    store._vm = new Vue({

        data: {

          $$state: state

        },

        computed

    })

第二问题:为什么更改state状态只能通过提交mutation的方式才能实现修改?

非严格模式下直接使用this.store.state.xxx修改state不会发生改变, 并且控制台不会发生报错。而使用严格模式下面会直接进行报错,原因在于使用严格模式的时候会检测store._committing(在Store的constructor中有定义)状态,当store._committing的值为false,表示不是通过mutation的方法修改的,你可以通过控制台直接操作store中的state数据,手动修改它,并打印此时store._committing的值。当为严格模式的时候,执行下面代码:


    // enable strict mode for new vm

    /* 使用严格模式,保证修改store只能通过mutation */

    if (store.strict) {

        enableStrictMode(store)

    }

    function enableStrictMode (store) {

      store._vm.$watch(function () { return this._data.$$state }, () => {

        if (process.env.NODE_ENV !== 'production') {

          /* 检测store中的_committing的值,如果是false代表不是通过mutation的方法修改的 */

          assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)

        }

      }, { deep: true, sync: true })

    }

所以说Vuex更改state状态是必须要通过显示提交mutation的方式,当调用commit()提交一个mutation的时候,store._committing的值就变为true。

更多推荐