vuex 源碼分析(一) 使用方法和代碼結構

来源:https://www.cnblogs.com/greatdesert/archive/2019/09/06/11431007.html
-Advertisement-
Play Games

Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式,它採用集中式存儲管理應用的所有組件的狀態,註意:使用前需要先載入vue文件才可以使用(在node.js下需要使用Vue.use(Vuex)來安裝vuex插件,在瀏覽器環境下直接載入即可,vuex會自行安裝) vuex的使用方法很簡單,首 ...


Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式,它採用集中式存儲管理應用的所有組件的狀態,註意:使用前需要先載入vue文件才可以使用(在node.js下需要使用Vue.use(Vuex)來安裝vuex插件,在瀏覽器環境下直接載入即可,vuex會自行安裝)

vuex的使用方法很簡單,首先調用new Vuex.Store(options)創建一個store實例即可,然後在創建vue實例時把這個store實例作為store屬性傳入即可,調用new Vuex.Store(options)創建一個vuex實例時可以傳入如下參數:

    state            存儲的數據                
    getters           可以認為是store的計算屬性
    mutations         這是更改Vuex的store里的數據的唯一方法,只能是同步方法(官網這樣寫的,其實不贊同這個說法,具體請看下麵)
    actions                  可以包含一些非同步操作,它提交的是mutation,而不是直接變更狀態。
    modules                為了更方便的管理倉庫,我們把一個大的store拆成一些modules(子倉庫),整個modules是一個樹型結構
    strict                是否開啟嚴格模式,無論何時發生了狀態變更且不是由mutation函數引起的,將會拋出錯誤,這能保證所有的狀態變更都能被調試工具跟蹤到。    ;預設為false
後面介紹每個api時單獨介紹用法,舉個慄子,如下:

 writer by:大沙漠 QQ:22969969

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/vuex.js"></script>
</head>
<body>
    <div id="app">
        <p>{{message}}</p>
        <p>{{reverseMessage}}</p>
        <p>{{no}}</p>
        <button @click="test1">mutation測試</button>
        <button @click="test2">action測試</button>
    </div>
    <script>
        const store = new Vuex.Store({
            state:{message:'Hello World',no:123},                   
            getters:{                                   //getters類似於Vue的計算屬性
                reverseMessage:state=>{return state.message.split('').reverse().join('')},
                increment:state=>{state.no++}
            },
            mutations:{                                 //mutation包含一些同步操作
                increment(state,payload){state.no+=payload.no}
            },
            actions:{                                   //actions包含一些非同步操作
                increment({commit},info){
                    setTimeout(function(){
                        commit('increment',info)
                    },500)
                }
            }
        })

        var app = new Vue({
            el:"#app",
            store,
            computed:{
                no:function(){return this.$store.state.no},
                message:function(){return this.$store.state.message},
                reverseMessage:function(){return this.$store.getters.reverseMessage}
            },
            methods:{
                test1:function(){this.$store.commit('increment',{no:10})},
                test2:function(){this.$store.dispatch('increment',{no:10})}
            }
        })
    </script>
</body>
</html>

渲染如下:

我們點擊mutation測試這個按鈕123會這個數字會立馬遞增10,而點擊action測試這個按鈕,數字會延遲0.5秒,再遞增10,前者是mutation對應的同步操作,而後者是action對應的非同步操作

如果只是這樣顯式數據,感覺vuex沒有什麼用處,我們在瀏覽器里輸入store.state.message="Hello Vue"來直接修改state里的數據看看怎麼樣,如下:

修改後頁面里的內容立即就變化了,如下:

是不是很神奇,這裡只是一個組件引用了vuex,如果很多的組件都引用了同一個vuex實例,那麼只要狀態發生變化,對應的組件都會自動更新,這就是vuex的作用。

vuex官網說mutations是更改store里數據的唯一方法,這在邏輯上不嚴謹的,只有設置了strict為true,那麼說mutations是更改store里數據的唯一方法還可以接收,比如我們在控制台里直接修改store里的數據了,也沒報錯啥的。

vuex內部的實現原理很簡單,就是定義一個vue實例,把vuex.store里的state作為data屬性(不是根data,而是放到$$state這個屬性里,不過由於值是個對象,因此也是響應式的),getters作為計算屬性來實現的

 

源碼分析


我們先看看vuex插件導出了哪些符號,打開vuex的源文件,拉到最底部,如下:

  var index = {
    Store: Store,                           //初始化
    install: install,                       //安裝方法
    version: '3.1.0',                       //版本號
    mapState: mapState,                     //State輔助函數
    mapMutations: mapMutations,             //Mutations輔助函數
    mapGetters: mapGetters,                 //Getters輔助函數
    mapActions: mapActions,                 //Actions輔助函數
    createNamespacedHelpers: createNamespacedHelpers
  };

可以看到Store就是初始化函數,install是安裝用的,version是版本號,其它幾個都是輔助函數,最後一個是和輔助函數的上下文綁定(也就是命名空間)相關,一般用不到。

我們先看看安裝流程,如下:

  function install (_Vue) {         //安裝Vuex
    if (Vue && _Vue === Vue) {        //如果Veue存在且等於參數_Vue,表示已經安裝過了,則報錯
      {
        console.error(
          '[vuex] already installed. Vue.use(Vuex) should be called only once.'
        );
      }
      return
    }
    Vue = _Vue;                       //將_Vue保存到局部變數Vue里   
    applyMixin(Vue);                  //調用applyMixin()進行初始化
  }

安裝時最後會執行applyMixin函數,該函數如下:

  function applyMixin (Vue) {                         //將Vuex混入到Vue裡面
    var version = Number(Vue.version.split('.')[0]);      //獲取主版本號

    if (version >= 2) {                                   //如果是Vue2.0及以上版
      Vue.mixin({ beforeCreate: vuexInit });                  //則執行Vue.mixin()方法,植入一個beforeCreate回調函數
    } else {
      // override init and inject vuex init procedure
      // for 1.x backwards compatibility.
      var _init = Vue.prototype._init;
      Vue.prototype._init = function (options) {
        if ( options === void 0 ) 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 () {                                //Vuex的安裝方法  
      var options = this.$options;
      // store injection
      if (options.store) {                                    //如果options.store存在,即初始化Vue實例時傳入了store實例
        this.$store = typeof options.store === 'function'         //則將store保存到大Vue的$store屬性上,如果store是個函數,則執行該函數
          ? options.store()
          : options.store;
      } else if (options.parent && options.parent.$store) {   //如果options.store不存在,但是父實例存在$store(組件的情況下)
        this.$store = options.parent.$store;                      //則設置this.$store為父實例的$store
      }
    }
  }

這樣不管是根vue實例,還是組件,都可以通過this.$store來獲取到對應的$store實例了,安裝就是這樣子,下麵說一下整體流程

以上面的例子為例,當我們執行new Vuex.Store()創建一個Vuex.Store的實例時會執行到導出符號的Store函數,如下:

  var Store = function Store (options) {                  //構造函數
    var this$1 = this;
    if ( options === void 0 ) 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) {      //如果局部變數Vue不存在且window.Vue存在,即已經引用了Vue,而且window.Vue不存在(還沒安裝)
      install(window.Vue);                                              //執行install()方法進行安裝     ;從這裡看出在瀏覽器環境下不需要執行Vue.use(vuex),在執行new Vuex.Store()會自己安裝
    }

    {
      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.");
    }

    var plugins = options.plugins; if ( plugins === void 0 ) plugins = [];
    var strict = options.strict; if ( strict === void 0 ) strict = false;

    // 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);                    //初始化modules,ModuleCollection對象是收集所有模塊信息的
    this._modulesNamespaceMap = Object.create(null);
    this._subscribers = [];
    this._watcherVM = new Vue();

    // 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) {          //重寫dispatch方法,將上下文設置為當前的this實例
      return dispatch.call(store, type, payload)
    };
    this.commit = function boundCommit (type, payload, options) {     //重寫commit方法,將上下文設置為當前的this實例
      return commit.call(store, type, payload, options)
    };

    // strict mode
    this.strict = strict;
  
    var state = this._modules.root.state;                             //獲取根倉庫的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);               //安裝根模塊,該函數會遞歸調用的安裝子模塊,並收集它們的getters到this._wrappendGetters屬性上

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreVM(this, state);                                        //安裝vm,也就是這裡會創建一個vue實例,並把state、getter作為響應式對象

    // apply plugins
    plugins.forEach(function (plugin) { return plugin(this$1); });    //安裝插件

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

ModuleCollection模塊會收集根模塊和子模塊的的所有信息,例子里執行到這裡時對應的this._modules如下:

然後會調用執行到installModule()會安裝每個模塊,也就是把每個模塊的getters、mutations、actions進行一系列處理,如果還有子模塊(module屬性)則遞歸調用installModule依次處理每個子模塊,如下:

function installModule (store, rootState, path, module, hot) {      //安裝模塊 
    var isRoot = !path.length;                                          //當前是否為根Module
    var namespace = store._modules.getNamespace(path);                  //獲取命名空間

    // register in namespace map
    if (module.namespaced) {
      store._modulesNamespaceMap[namespace] = module;
    }

    // set state  
    if (!isRoot && !hot) {                                             
      var parentState = getNestedState(rootState, path.slice(0, -1)); 
      var moduleName = path[path.length - 1];                          
      store._withCommit(function () { 
        Vue.set(parentState, moduleName, module.state);
      });
    }

    var local = module.context = makeLocalContext(store, namespace, path);

    module.forEachMutation(function (mutation, key) {                     //遍歷module模塊的mutations對象
      var namespacedType = namespace + key;
      registerMutation(store, namespacedType, mutation, local);             //調用registerMutation註冊mutation
    });

    module.forEachAction(function (action, key) {                         //遍歷module模塊的actions對象
      var type = action.root ? key : namespace + key; 
      var handler = action.handler || action;
      registerAction(store, type, handler, local);                          //調用registerAction註冊action
    });

    module.forEachGetter(function (getter, key) {                         //遍歷module模塊的getter對象
      var namespacedType = namespace + key;
      registerGetter(store, namespacedType, getter, local);                 //調用registerGetter註冊getter
    });

    module.forEachChild(function (child, key) {                           //如果有定義了module(存在子模塊的情況)
      installModule(store, rootState, path.concat(key), child, hot);        //則遞歸調用installModule
    });
  }

最後會執行resetStoreVM()函數,該函數內部會創建一個vue實例,這樣state和getters就是響應式數據了,如下:

  function resetStoreVM (store, state, hot) {         //重新存儲數據
    var oldVm = store._vm;

    // bind store public getters
    store.getters = {};
    var wrappedGetters = store._wrappedGetters;               //獲取store的所有getter數組信息
    var computed = {};
    forEachValue(wrappedGetters, function (fn, key) {         //遍歷wrappedGetters
      // use computed to leverage its lazy-caching mechanism
      computed[key] = function () { return fn(store); };        //將getter保存到computed裡面
      Object.defineProperty(store.getters, key, {
        get: function () { return 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
    var silent = Vue.config.silent;                           //保存Vue.config.silent的配置
    Vue.config.silent = true;                                 //設置Vue.config.silent配置屬性為true(先關閉警告)
    store._vm = new Vue({                                     //創建new Vue()實例把$$state和computed變成響應式的
      data: {
        $$state: state
      },
      computed: computed
    });
    Vue.config.silent = silent;                               //將Vue.config.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(function () {
          oldVm._data.$$state = null;
        });
      }
      Vue.nextTick(function () { return oldVm.$destroy(); });
    }
  }

這樣整個流程就跑完了,就是內部創建一個vue實例,利用vue的響應式做數據動態響應。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一個box中,點擊其中的任意位置,會有煙花從正下方升起到點擊處,並燃放出一圈圓形的煙花。代碼如下: 首先是佈局以及樣式: 只給一個盒子的佈局,燃放的煙花以及綻放的小煙花都是中js代碼中創建。 js代碼: 順便把我封裝的一些實用的函數分享出來: 若有不足,還望指出。可以一起互相交流學習一下 ...
  • 問題講解:在使用vue版本的ElementUI中的table功能的時候還是遇到了一些問題,可以說餓了麽團隊在這個UI框架的文檔撰寫已經非常不錯了,不過還是有一些方法乍一看讓人摸不著頭腦,有些table的常用功能示例代碼提供的不是非常詳細,所以這次針對這個可展開表格實現手風琴效果寫一篇博客探討一下。 ...
  • 1.nodejs安裝,地址 https://nodejs.org/en/ 2.在本地項目中運行npm run build 命令將開發好的項目打包生成.nuxt文件夾,然後把.nuxt文件夾、nuxt.config.js、static文件夾、package.json (如果有自己創建的文件夾也需拷貝) ...
  • 首先在要使用的靜態文件代碼中引入‘./sliderVerify/sliderVerify.js‘ 先看看效果 示例代碼 另外 如果你不是用的layui的form提交 那麼這裡提供了一種方法 可以拿去放在你自己想要驗證的form中 作為校驗依據 或者你可以配置一下滑動成功時的回調 使用該方法可以將組件 ...
  • <!doctype html><!--聲明文檔類型 網頁文檔--> <html lang="en"><!--根目錄標簽 父級--> <head><!--網頁的頭部 用戶看不見的--> <meta charset='UTF-8'><!--國際編碼 字元的編碼格式--> <!--網頁三要素 title ... ...
  • 過去十年,是前端覺醒的十年。 前端這個行業很年輕,更早時候其實是有前端工作但是沒有前端崗位。大家覺得這個東西程式員做也行,設計師做也行。前端工作一直存在,但是沒有人認為它是一個獨立的工種和崗位,也沒有人去發展它的工程體系,更沒有人去找它的核心價值。所以如果說過去十年前端是什麼樣的十年,我認為是覺醒的 ...
  • 本來是想做一個滑鼠點擊事件:A,B兩個東西,B先隱藏,點擊A,B出現,再點一次A,B消失,然後發現在判斷不同的情況下,出現了一點小問題 暫時沒有問題的寫法: 然後,我把它的判斷條件改了一下,其實這是我第一次寫的想法: 然後它的效果是,第一次點擊的時候沒有反應,要點第二次,B才會出來,有點不明白 ...
  • (馬蜂窩技術原創內容,公眾號 ID:mfwtech) 一份來自 Akamai 的研究報告顯示,在對 1048 名網購戶進行採訪後發現: 約 47% 的用戶期望他們的頁面在兩秒之內載入完成。 如果頁面載入時間超過 3s,約 40% 的用戶會選擇離開或關閉頁面。 約 47% 的用戶期望他們的頁面在兩秒之 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...