Vuex 工作原理

Posted everlose

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vuex 工作原理相关的知识,希望对你有一定的参考价值。

环境和测试代码

vue 2.6.11, vuex 3.1.3

<!DOCTYPE html>
<html>
<head>
  <title>vue test</title>
</head>
<body>
<div id="app">
  <button @click="doAdd">do add 2</button>
  {{vCount}}
  <button-counter></button-counter>
</div>

  <!-- Vue.js v2.6.11 -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://cdn.bootcss.com/vuex/3.1.3/vuex.js"></script>
  <script>
    Vue.component(‘button-counter‘, {
      data: function () {
        return {}
      },
      mounted() {
        console.log(‘child ‘, this.$store);
      },
      template: ‘<button v-on:click="doChildAdd">do add 5</button>‘,
      methods: {
        doChildAdd() {
          store.commit(‘increment‘, 5)
        }
      }
    });
    const store = new Vuex.Store({
      state: {
        count: 1
      },
      mutations: {
        increment (state, n = 1) {
          state.count += n;
        }
      }
    })
    var app = new Vue({
      el: ‘#app‘,
      store,
      computed: {
        vCount () {
          return this.$store.state.count
        },
      },
      mounted() {
        console.log(‘parent ‘, this.$store);
      },
      methods: {
        doAdd() {
          store.commit(‘increment‘, 2)
        }
      }
    })

    console.log(app);
    // var event = new CustomEvent(‘test‘, { ‘detail‘: 5 }); window.dispatchEvent(event);
  </script>
</body>
</html>

总结

侵入每个 vue 组件注册了 $store 属性,而所有 $store 属性都指向一个 store 实例,这样就能做到所有 vue 组件访问的都是同一份全局变量。

vue 组件里用户定义取 store 上的变量用于渲染或者其他逻辑,而后改动 this.store.xxx 时,vue 本身核心依赖收集能知道要更新哪些视图,就完成了。

分析

技术图片

首先给出 store 的定义代码,注意下文的 installModule 和 resetStoreVM,这是两个关键的步骤

var Store = function Store (options) {
  // ...

  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 = [];


  // bind commit and dispatch to self
  var store = this;
  var ref = this;
  var dispatch = ref.dispatch;
  var commit = ref.commit;
  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)
  };
  
  var 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)
  resetStoreVM(this, state);

  // ...
};

resetStoreVM(this, state) 方法

store._vm = new Vue({
  data: {
    $$state: state
  },
  computed: computed
});

install(window.Vue); 方法是用来挂载 store 对象到 vue 实例的,里面做了件事 Vue.mixin({ beforeCreate: vuexInit });

vuexInit 函数里就做了把 store 复制给 vue 组件的 this.$store 属性.

function vuexInit () {
  var 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;
  }
}

commit

下文关键部分是 this._withCommit 里的匿名函数,它遍历 entry,执行用户自定义的 handler 处理函数,

就上文的测试代码来说,这个 handler 就是 commit 里的函数 increment (state, n = 1) { state.count += n; }

接着 handler 里要读取 state.count,就会去获取 state,return this._vm._data.$$state

Store.prototype.commit = function commit(_type, _payload, _options) {
  var this$1 = this;

  // check object-style commit
  var ref = unifyObjectStyle(_type, _payload, _options);
  var type = ref.type;
  var payload = ref.payload;
  var options = ref.options;

  var mutation = { type: type, payload: payload };
  var entry = this._mutations[type];
  
  this._withCommit(function() {
    entry.forEach(function commitIterator(handler) {
      handler(payload);
    });
  });

  // ...
};

// _withCommit 执行它所传入的 fn,它遍历 entry,执行用户自定义的 handler 处理函数,
// 这个 handler 就是我们定义的 commit 里的函数 increment (state, n = 1) { state.count += n; },总之要变动 state.count,就会进入 state 的拦截器, 

prototypeAccessors$1.state.get = function () {
  return this._vm._data.$$state
};

// 一旦触发去 vue 的 _data 上有 vue 自己的拦截器 get,而动作做 state.count += n 后,就触发了 vue 自己拦截器里的 set。最后这样就开始vue自身的渲染逻辑。

最后修改了 state.count 后,等于说用户也变动了在 vue 组件里那个 computed,自然而然的进入 vue 组件自身的 get/set 以及渲染逻辑。

computed: {
  vCount () {
    return this.$store.state.count
  },
},

dispatch

dispatch 与 commit 的流程大体相同,不同点是他会使用 Promise.all 来保证 handler 函数的异步触发,并且最后也会 return 一个 promise 对象出去而已。

Store.prototype.dispatch = function dispatch(_type, _payload) {
  var this$1 = this;

  // check object-style dispatch
  var ref = unifyObjectStyle(_type, _payload);
  var type = ref.type;
  var payload = ref.payload;

  var action = { type: type, payload: payload };
  var entry = this._actions[type];

    entry.length > 1
      ? Promise.all(
          entry.map(function(handler) {
            return handler(payload);
          })
        )
      : entry[0](payload);

  return result.then(function(res) {
    return res;
  });
};

以上是关于Vuex 工作原理的主要内容,如果未能解决你的问题,请参考以下文章

Vue2.0学习—Vuex工作原理图(二十五)

Vue2.0学习—Vuex工作原理图(二十五)

vue的使用及工作原理源码分析

vuex的原理以及实现

Vuex原理实现

Vuex原理实现