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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM