短短几行代碼搞懂 vuex 的核心


短短几行代碼搞懂 vuex 的核心


前言

我們都知道在用 vue 的時候,簡單的父子通信和 EventBus 已經不能滿足我們的要求了,嵌套層級過多和難以追蹤改變是兩個較為主要的問題,這個時候可以用 vuex 來解決,想必大家都用過,所以今天跟大家分享的是 vuex 的簡單實現,真的是超簡單,就幾行代碼(文章結尾有鏈接),帶你領略 vuex 的精髓,並且在最後會有幾個問題答疑(比如時光穿梭、本地持久化等)幫大家鞏固一下 vuex。

前置知識

vuex 是基於 vue 的狀態管理工具,通俗點講講就是變量共享,你要知道 this.$store 本質是個對象,大家可以看下下面這張圖,看看 this.$store 到底是個啥(只看有標號的行即可):

短短几行代碼搞懂 vuex 的核心

還記得我們是怎麼使用 vuex 的麼?


<code>import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex); // 這是插件固定寫法,沒有什麼為什麼,官網寫的很清楚,這樣寫 vue 會自動調用插件的 install 方法
const store = new Vuex.Store({
state: {...},
mutations: {...},
actions: {...},
getters: {...},
})
new Vue({
el: '#app',
store
})
複製代碼/<code>

很顯然,Store 裡面大致就傳入四個參數,目前我們也只需要這些參數就夠了,四個參數又可以分為兩類,一類是獲取數據,一類是修改數據,就像下面這張圖畫的一樣:

短短几行代碼搞懂 vuex 的核心

我感覺上面這張圖畫的還挺不錯,應該挺明瞭。組件和 store 是分開的,組件獲取數據可以通過 $store.state 和 $store.getters,組件要修改數據可以通過 $store.actions 和 $store.mutations(當然最終都是 mutations,這也使得便於追蹤狀態的改變),如此一來,形成了閉環,也符合我們所說的單向數據流的思想,只有一個地方能改數據,不能遍地開花,不然就亂套了。另外 store 也是個單例模式的例子,所有的組件都共用一個全局的 store,每個組件的 $store 都是同一個東西,這點也很好理解。
再補充一句,很多人總是記不清 actions 和 mutations 到底哪個是異步哪個是同步,不妨試試這樣記:actions 和 async 都是 a 開頭的,所以 actions 是異步,或者 actions 是要調用 api 的,所以是異步。


簡單實現

有了上面這些概念,接下來我們就簡單用幾行代碼來簡單實現一下吧。首先先寫個簡單的小框架,就像下面這樣(官網上有說明插件的編寫方式):

<code>

複製代碼/<code>

ok,然後我們在 install 裡面寫 vuex 的代碼即可,現在我們只需要聲明一個 store 對象並且賦予 store 兩個屬性(這裡以 state 和 mutations 舉例),這個我用很簡單的代碼演示一下大家就清楚了:

<code> Vuex.install = function (Vue) {
console.log('install 方法開始執行')
const store = {} // 聲明一個對象
store.state = new Vue({ // 賦予 state 屬性,用來獲取值
data () {
return {
msg: '哈哈'
}
}
})
store.mutations = { // 賦予 mutations 屬性,用來修改值
SETMSG(value) {
store.state.msg = value
}
}
}
複製代碼/<code>

上面的代碼中要注意的是 state 其實是利用了 vue 的響應式原理,使用了 new Vue(),由此 state 就變成響應式的了,這是 vue 的特性。注意這裡單純的使用 Object.defineProperty 來定義 state 是不行的,是無法更新視圖的,因為它並沒有被 vue 進行依賴收集,所以說 vuex 是強依賴於 vue 的。還有就是一開始我們也要把 state 的數據寫全,不然後面添加的也是無響應更新的,大家如果有用到過 $set 應該能體會到為什麼有時候數據變了,視圖沒更新那種感覺。

,現在我們已經有了 store,接下來就是把它掛到每個組件下面了,這裡我們使用 Vue.mixin 的混入方式,代碼如下:

<code>Vue.mixin({ // 也是固定寫法:每個組件都會執行下面這個生命週期 

beforeCreate () {
this.$store = store // 於是每個組件都會有 this.$store,並且都指向同一個 store
}
})
複製代碼/<code>

當然用 Vue.prototype.$store = store 也能達到同樣的效果,事實上,vue-router 也是同樣的方式我們才能在每個實例中用 this.$router 來調用。另外如果要說 mixin 和 prototype 這兩種掛載方式的區別,我就想到兩小點:

  • mixin 是在 vue 實例上,prototype 是在原型上
  • 把 store 掛載在實例上,就不用順著原型鏈查找了,提升了一丟丟性能

不知道大家還知道其他原因嗎,歡迎在下面留言。

好了,至此,我們大概就寫完了一個簡單的 demo,現在寫個例子來測試一下:

<code>let v1 = new Vue({
el: '#component1',
computed: {
data() {
return this.$store.state.msg
}
}
})
let v2 = new Vue({
el: '#component2',
methods: {
change() {
this.$store.mutations.SETMSG('這是 mutations 觸發的值')
}

}
})
console.log(v1, v2)
複製代碼/<code>

下面是測試的結果:

短短几行代碼搞懂 vuex 的核心

當然我們平時用 mutations 是通過 commit 來寫的,其實 commit 就是個函數,本質上也是調用 mutations,這裡也順手簡單寫下 commit:


<code>Vuex.install = function (Vue) {
...
store.commit = function(mutationName, value) {

store.mutations[mutationName] && store.mutations[mutationName](value)
}
...
}
...
let v2 = new Vue({
el: '#component2',
methods: {
change() { // 改一下調用方式,結果是一樣的
// this.$store.mutations.SETMSG('這是 mutations 觸發的值')
this.$store.commit('SETMSG', '這是 mutations 觸發的值')
}
}
})
複製代碼/<code>

最終效果是一樣的,這裡就不展示了。 深吸一口氣,目前為止我們已經實現超簡版的 vuex,接下來是幾個問題答疑。

問題答疑

為什麼需要 getters

這個東西其實和我們平時寫的計算屬性一毛一樣,state 和 getters 的關係好比 data 和 computed,大家細品一下。

如何區分 state 是不是通過 commit 修改的

我們知道 vuex 中修改 state 就一個通道,就是執行 commit,但其實你不通過 commit 也是能改的,那怎麼知道它是通過 commit 修改的呢?就是在執行 commit 的時候加個標誌位 _committing,執行 commit 的時候將 _committing 設置為 true,_committing 為 true 才能修改 state,而其他方式修改的 state 並不會修改 _committing 標誌位,這樣一來就能判斷是不是通過 commit 修改的。 如果你在 vuex 中打開了嚴格模式,任何非 mutation 更改都會拋出錯誤。

mapState 等輔助函數的實現

這一類輔助函數本質就是語法糖,這裡我們以 mapState 舉例子,我們回顧一下用法:

<code>import {mapState} from 'vuex'
export default{
computed:{
...mapState(['msg','user'])
}
}
複製代碼/<code>

然後我們在頁面中就能用 this.msg 訪問,其實調用的還是 this.$store.state.msg,只不過寫起來簡單點。下面我們看下怎麼簡單實現,很顯然,這裡 mapState 是一個函數,接收一個數組:

<code>function mapState (list) {
let obj = {}
list.forEach(stateName => {
obj[stateName] = () => this.$store.state[stateName]
})
return obj
}
複製代碼/<code>

是的,就這麼點代碼,其他 map 輔助函數也是也一樣的道理。

本地持久化

  • 我們可以在每次 commit 的時候把 state 保存到 localStorage 或者 sessionStorage 中,然後頁面初始化的時候,先讀取本地存儲的 state 值,不過要注意頻繁存儲的問題。
  • 我們可以利用 beforeunload 這個事件,在頁面卸載之前再把 state 的值存起來,這樣效率也挺高的。

時光穿梭

這是 devtoolPlugin 提供的功能,因為開發的時候所有 state 的改變都有記錄,“時空穿梭”的功能其實就是能夠讓我們回到或去到某一狀態,實際上就是將當前的 state 替換為記錄中的某個 state,我們看下 vuex 的 store 中就為我們提供了一個這樣的函數 replaceState(真的是直接替換),具體代碼如下:

<code>replaceState (state) {
this._withCommit(() => { // 這裡面就是上面說到的 _commiting 標識
this._vm.state = state
})
}
複製代碼/<code>

下面是兩個個小截圖,希望能夠幫助你理解:

短短几行代碼搞懂 vuex 的核心

短短几行代碼搞懂 vuex 的核心


小結

如果硬要說 vuex 中最核心的一個點的話,那就是利用了 vue 的響應式,我想這是最為主要的。最後希望本篇文章能夠對你有所幫助,不知道寫的清不清楚,也祝大家百毒不侵,開開心心上班,回見。

github:https://github.com/lgq627628/2020/blob/master/%E6%89%8B%E5%86%99%E5%8E%9F%E7%94%9F/Vuex/vuex3.js


分享到:


相關文章: