試了了解一下這個。
我們在使用Vue.js開發複雜的應用時,經常會遇到多個元件共享同一個狀態,亦或是多個元件會去更新同一個狀態,在應用程式碼量較少的時候,我們可以元件間通訊去維護修改資料,或者是透過事件匯流排來進行資料的傳遞以及修改。但是當應用逐漸龐大以後,程式碼就會變得難以維護,從父元件開始透過prop傳遞多層巢狀的資料由於層級過深而顯得異常脆弱,而事件匯流排也會因為元件的增多、程式碼量的增大而顯得互動錯綜複雜,難以捋清其中的傳遞關係。
那麼為什麼我們不能將資料層與元件層抽離開來呢?把資料層放到全域性形成一個單一的Store,元件層變得更薄,專門用來進行資料的展示及操作。所有資料的變更都需要經過全域性的Store來進行,形成一個單向資料流,使資料變化變得“可預測”。
Vuex是一個專門為Vue.js框架設計的、用於對Vue.js應用程式進行狀態管理的庫,它借鑑了Flux、redux的基本思想,將共享的資料抽離到全域性,以一個單例存放,同時利用Vue.js的響應式機制來進行高效的狀態管理與更新。正是因為Vuex使用了Vue.js內部的“響應式機制”,所以Vuex是一個專門為Vue.js設計並與之高度契合的框架(優點是更加簡潔高效,缺點是隻能跟Vue.js搭配使用)。具體使用方法及API可以參考Vuex的官網。
先來看一下這張Vuex的資料流程圖,熟悉Vuex使用的同學應該已經有所瞭解。
Vuex實現了一個單向資料流,在全域性擁有一個State存放資料,所有修改State的操作必須透過Mutation進行,Mutation的同時提供了訂閱者模式供外部外掛呼叫獲取State資料的更新。所有非同步介面需要走Action,常見於呼叫後端介面非同步獲取更新資料,而Action也是無法直接修改State的,還是需要透過Mutation來修改State的資料。最後,根據State的變化,渲染到檢視上。Vuex執行依賴Vue內部資料雙向繫結機制,需要new一個Vue物件來實現“響應式化”,所以Vuex是一個專門為Vue.js設計的狀態管理庫。
使用過Vuex的朋友一定知道,Vuex的安裝十分簡單,只需要提供一個store,然後執行下面兩句程式碼即完成的Vuex的引入。
那麼問題來了,Vuex是怎樣把store注入到Vue例項中去的呢?
Vue.js提供了Vue.use方法用來給Vue.js安裝外掛,內部透過呼叫外掛的install方法(當外掛是一個物件的時候)來進行外掛的安裝。
我們來看一下Vuex的install實現。
這段install程式碼做了兩件事情,一件是防止Vuex被重複安裝,另一件是執行applyMixin,目的是執行vuexInit方法初始化Vuex。Vuex針對Vue1.0與2.0分別進行了不同的處理,如果是Vue1.0,Vuex會將vuexInit方法放入Vue的_init方法中,而對於Vue2.0,則會將vuexinit混淆進Vue的beforeCreacte鉤子中。來看一下vuexInit的程式碼。
vuexInit會嘗試從options中獲取store,如果當前元件是根元件(Root節點),則options中會存在store,直接獲取賦值給$store即可。如果當前元件非根元件,則透過options中的parent獲取父元件的$store引用。這樣一來,所有的元件都獲取到了同一份記憶體地址的Store例項,於是我們可以在每一個元件中透過this.$store愉快地訪問全域性的Store例項了。
那麼,什麼是Store例項?
我們傳入到根元件到store,就是Store例項,用Vuex提供到Store方法構造。
我們來看一下Store的實現。首先是建構函式。
Store的構造類除了初始化一些內部變數以外,主要執行了installModule(初始化module)以及resetStoreVM(透過VM使store“響應式”)。
installModule的作用主要是用為module加上namespace名字空間(如果有)後,註冊mutation、action以及getter,同時遞迴安裝所有子module。
在說resetStoreVM之前,先來看一個小demo。
上述程式碼在全域性有一個globalData,它被傳入一個Vue物件的data中,之後在任意Vue模板中對該變數進行展示,因為此時globalData已經在Vue的prototype上了所以直接透過this.prototype訪問,也就是在模板中的{{prototype.d}}。此時,setTimeout在1s之後將globalData.d進行修改,我們發現模板中的globalData.d發生了變化。其實上述部分就是Vuex依賴Vue核心實現資料的“響應式化”。
不熟悉Vue.js響應式原理的同學可以透過筆者另一篇文章響應式原理了解Vue.js是如何進行資料雙向繫結的。
接著來看程式碼。
resetStoreVM首先會遍歷wrappedGetters,使用Object.defineProperty方法為每一個getter繫結上get方法,這樣我們就可以在元件裡訪問this.$store.getter.test就等同於訪問store._vm.test。
之後Vuex採用了new一個Vue物件來實現資料的“響應式化”,運用Vue.js內部提供的資料雙向繫結功能來實現store的資料與檢視的同步更新。
這時候我們訪問store._vm.test也就訪問了Vue例項中的屬性。
這兩步執行完以後,我們就可以透過this.$store.getter.test訪問vm中的test屬性了。
Vuex的Store構造類的option有一個strict的引數,可以控制Vuex執行嚴格模式,嚴格模式下,所有修改state的操作必須透過mutation實現,否則會丟擲錯誤。
首先,在嚴格模式下,Vuex會利用vm的$watch方法來觀察$$state,也就是Store的state,在它被修改的時候進入回撥。我們發現,回撥中只有一句話,用assert斷言來檢測store._committing,當store._committing為false的時候會觸發斷言,丟擲異常。
我們發現,Store的commit方法中,執行mutation的語句是這樣的。
再來看看_withCommit的實現。
我們發現,透過commit(mutation)修改state資料的時候,會再呼叫mutation方法之前將committing置為true,接下來再透過mutation函式修改state中的資料,這時候觸發$watch中的回撥斷言committing是不會丟擲異常的(此時committing為true)。而當我們直接修改state的資料時,觸發$watch的回撥執行斷言,這時committing為false,則會丟擲異常。這就是Vuex的嚴格模式的實現。
接下來我們來看看Store提供的一些API。
commit方法會根據type找到並呼叫_mutations中的所有type對應的mutation方法,所以當沒有namespace的時候,commit方法會觸發所有module中的mutation方法。再執行完所有的mutation之後會執行_subscribers中的所有訂閱者。我們來看一下_subscribers是什麼。
Store給外部提供了一個subscribe方法,用以註冊一個訂閱函式,會push到Store例項的_subscribers中,同時返回一個從_subscribers中登出該訂閱者的方法。
在commit結束以後則會呼叫這些_subscribers中的訂閱者,這個訂閱者模式提供給外部一個監視state變化的可能。state透過mutation改變時,可以有效補獲這些變化。
來看一下dispatch的實現。
以及registerAction時候做的事情。
因為registerAction的時候將push進_actions的action進行了一層封裝(wrappedActionHandler),所以我們在進行dispatch的第一個引數中獲取state、commit等方法。之後,執行結果res會被進行判斷是否是Promise,不是則會進行一層封裝,將其轉化成Promise物件。dispatch時則從_actions中取出,只有一個的時候直接返回,否則用Promise.all處理再返回。
熟悉Vue的朋友應該很熟悉watch這個方法。這裡採用了比較巧妙的設計,_watcherVM是一個Vue的例項,所以watch就可以直接採用了Vue內部的watch特性提供了一種觀察資料getter變動的方法。
registerModule用以註冊一個動態模組,也就是在store建立以後再註冊模組的時候用該介面。內部實現實際上也只有installModule與resetStoreVM兩個步驟,前面已經講過,這裡不再累述。
這裡的resetStore其實也就是將store中的_actions等進行初始化以後,重新執行installModule與resetStoreVM來初始化module以及用Vue特性使其“響應式化”,這跟建構函式中的是一致的。
Vue提供了一個非常好用的外掛Vue.js devtools
如果已經安裝了該外掛,則會在windows物件上暴露一個VUE_DEVTOOLS_GLOBAL_HOOK。devtoolHook用在初始化的時候會觸發“vuex:init”事件通知外掛,然後透過on方法監聽“vuex:travel-to-state”事件來重置state。最後透過Store的subscribe方法來新增一個訂閱者,在觸發commit方法修改mutation資料以後,該訂閱者會被通知,從而觸發“vuex:mutation”事件。
Vuex是一個非常優秀的庫,程式碼量不多且結構清晰,非常適合研究學習其內部實現。最近的一系列原始碼閱讀也使我自己受益匪淺,寫這篇文章也希望可以幫助到更多想要學習探索Vuex內部實現原理的同學。
試了了解一下這個。
Vuex我們在使用Vue.js開發複雜的應用時,經常會遇到多個元件共享同一個狀態,亦或是多個元件會去更新同一個狀態,在應用程式碼量較少的時候,我們可以元件間通訊去維護修改資料,或者是透過事件匯流排來進行資料的傳遞以及修改。但是當應用逐漸龐大以後,程式碼就會變得難以維護,從父元件開始透過prop傳遞多層巢狀的資料由於層級過深而顯得異常脆弱,而事件匯流排也會因為元件的增多、程式碼量的增大而顯得互動錯綜複雜,難以捋清其中的傳遞關係。
那麼為什麼我們不能將資料層與元件層抽離開來呢?把資料層放到全域性形成一個單一的Store,元件層變得更薄,專門用來進行資料的展示及操作。所有資料的變更都需要經過全域性的Store來進行,形成一個單向資料流,使資料變化變得“可預測”。
Vuex是一個專門為Vue.js框架設計的、用於對Vue.js應用程式進行狀態管理的庫,它借鑑了Flux、redux的基本思想,將共享的資料抽離到全域性,以一個單例存放,同時利用Vue.js的響應式機制來進行高效的狀態管理與更新。正是因為Vuex使用了Vue.js內部的“響應式機制”,所以Vuex是一個專門為Vue.js設計並與之高度契合的框架(優點是更加簡潔高效,缺點是隻能跟Vue.js搭配使用)。具體使用方法及API可以參考Vuex的官網。
先來看一下這張Vuex的資料流程圖,熟悉Vuex使用的同學應該已經有所瞭解。
Vuex實現了一個單向資料流,在全域性擁有一個State存放資料,所有修改State的操作必須透過Mutation進行,Mutation的同時提供了訂閱者模式供外部外掛呼叫獲取State資料的更新。所有非同步介面需要走Action,常見於呼叫後端介面非同步獲取更新資料,而Action也是無法直接修改State的,還是需要透過Mutation來修改State的資料。最後,根據State的變化,渲染到檢視上。Vuex執行依賴Vue內部資料雙向繫結機制,需要new一個Vue物件來實現“響應式化”,所以Vuex是一個專門為Vue.js設計的狀態管理庫。
安裝使用過Vuex的朋友一定知道,Vuex的安裝十分簡單,只需要提供一個store,然後執行下面兩句程式碼即完成的Vuex的引入。
那麼問題來了,Vuex是怎樣把store注入到Vue例項中去的呢?
Vue.js提供了Vue.use方法用來給Vue.js安裝外掛,內部透過呼叫外掛的install方法(當外掛是一個物件的時候)來進行外掛的安裝。
我們來看一下Vuex的install實現。
這段install程式碼做了兩件事情,一件是防止Vuex被重複安裝,另一件是執行applyMixin,目的是執行vuexInit方法初始化Vuex。Vuex針對Vue1.0與2.0分別進行了不同的處理,如果是Vue1.0,Vuex會將vuexInit方法放入Vue的_init方法中,而對於Vue2.0,則會將vuexinit混淆進Vue的beforeCreacte鉤子中。來看一下vuexInit的程式碼。
vuexInit會嘗試從options中獲取store,如果當前元件是根元件(Root節點),則options中會存在store,直接獲取賦值給$store即可。如果當前元件非根元件,則透過options中的parent獲取父元件的$store引用。這樣一來,所有的元件都獲取到了同一份記憶體地址的Store例項,於是我們可以在每一個元件中透過this.$store愉快地訪問全域性的Store例項了。
那麼,什麼是Store例項?
Store我們傳入到根元件到store,就是Store例項,用Vuex提供到Store方法構造。
我們來看一下Store的實現。首先是建構函式。
Store的構造類除了初始化一些內部變數以外,主要執行了installModule(初始化module)以及resetStoreVM(透過VM使store“響應式”)。
installModuleinstallModule的作用主要是用為module加上namespace名字空間(如果有)後,註冊mutation、action以及getter,同時遞迴安裝所有子module。
resetStoreVM在說resetStoreVM之前,先來看一個小demo。
上述程式碼在全域性有一個globalData,它被傳入一個Vue物件的data中,之後在任意Vue模板中對該變數進行展示,因為此時globalData已經在Vue的prototype上了所以直接透過this.prototype訪問,也就是在模板中的{{prototype.d}}。此時,setTimeout在1s之後將globalData.d進行修改,我們發現模板中的globalData.d發生了變化。其實上述部分就是Vuex依賴Vue核心實現資料的“響應式化”。
不熟悉Vue.js響應式原理的同學可以透過筆者另一篇文章響應式原理了解Vue.js是如何進行資料雙向繫結的。
接著來看程式碼。
resetStoreVM首先會遍歷wrappedGetters,使用Object.defineProperty方法為每一個getter繫結上get方法,這樣我們就可以在元件裡訪問this.$store.getter.test就等同於訪問store._vm.test。
之後Vuex採用了new一個Vue物件來實現資料的“響應式化”,運用Vue.js內部提供的資料雙向繫結功能來實現store的資料與檢視的同步更新。
這時候我們訪問store._vm.test也就訪問了Vue例項中的屬性。
這兩步執行完以後,我們就可以透過this.$store.getter.test訪問vm中的test屬性了。
嚴格模式Vuex的Store構造類的option有一個strict的引數,可以控制Vuex執行嚴格模式,嚴格模式下,所有修改state的操作必須透過mutation實現,否則會丟擲錯誤。
首先,在嚴格模式下,Vuex會利用vm的$watch方法來觀察$$state,也就是Store的state,在它被修改的時候進入回撥。我們發現,回撥中只有一句話,用assert斷言來檢測store._committing,當store._committing為false的時候會觸發斷言,丟擲異常。
我們發現,Store的commit方法中,執行mutation的語句是這樣的。
再來看看_withCommit的實現。
我們發現,透過commit(mutation)修改state資料的時候,會再呼叫mutation方法之前將committing置為true,接下來再透過mutation函式修改state中的資料,這時候觸發$watch中的回撥斷言committing是不會丟擲異常的(此時committing為true)。而當我們直接修改state的資料時,觸發$watch的回撥執行斷言,這時committing為false,則會丟擲異常。這就是Vuex的嚴格模式的實現。
接下來我們來看看Store提供的一些API。
commit(mutation)commit方法會根據type找到並呼叫_mutations中的所有type對應的mutation方法,所以當沒有namespace的時候,commit方法會觸發所有module中的mutation方法。再執行完所有的mutation之後會執行_subscribers中的所有訂閱者。我們來看一下_subscribers是什麼。
Store給外部提供了一個subscribe方法,用以註冊一個訂閱函式,會push到Store例項的_subscribers中,同時返回一個從_subscribers中登出該訂閱者的方法。
在commit結束以後則會呼叫這些_subscribers中的訂閱者,這個訂閱者模式提供給外部一個監視state變化的可能。state透過mutation改變時,可以有效補獲這些變化。
dispatch(action)來看一下dispatch的實現。
以及registerAction時候做的事情。
因為registerAction的時候將push進_actions的action進行了一層封裝(wrappedActionHandler),所以我們在進行dispatch的第一個引數中獲取state、commit等方法。之後,執行結果res會被進行判斷是否是Promise,不是則會進行一層封裝,將其轉化成Promise物件。dispatch時則從_actions中取出,只有一個的時候直接返回,否則用Promise.all處理再返回。
watch熟悉Vue的朋友應該很熟悉watch這個方法。這裡採用了比較巧妙的設計,_watcherVM是一個Vue的例項,所以watch就可以直接採用了Vue內部的watch特性提供了一種觀察資料getter變動的方法。
registerModuleregisterModule用以註冊一個動態模組,也就是在store建立以後再註冊模組的時候用該介面。內部實現實際上也只有installModule與resetStoreVM兩個步驟,前面已經講過,這裡不再累述。
resetStore這裡的resetStore其實也就是將store中的_actions等進行初始化以後,重新執行installModule與resetStoreVM來初始化module以及用Vue特性使其“響應式化”,這跟建構函式中的是一致的。
外掛Vue提供了一個非常好用的外掛Vue.js devtools
如果已經安裝了該外掛,則會在windows物件上暴露一個VUE_DEVTOOLS_GLOBAL_HOOK。devtoolHook用在初始化的時候會觸發“vuex:init”事件通知外掛,然後透過on方法監聽“vuex:travel-to-state”事件來重置state。最後透過Store的subscribe方法來新增一個訂閱者,在觸發commit方法修改mutation資料以後,該訂閱者會被通知,從而觸發“vuex:mutation”事件。
最後Vuex是一個非常優秀的庫,程式碼量不多且結構清晰,非常適合研究學習其內部實現。最近的一系列原始碼閱讀也使我自己受益匪淺,寫這篇文章也希望可以幫助到更多想要學習探索Vuex內部實現原理的同學。