一篇文章帶你吃透Vuex3的狀態管理

一. Vuex是什麼

瞭解過Vuex的都知道,它是Vue官方的狀態管理方案;可以對父子,祖孫以及兄弟組件之間進行通信;

除瞭使用Vuex,我們都知道還有一個方案能滿足任何組件的之間的通信,那就是vue全局事件總線($ bus)

在數據接收組件內利用$ on綁定一個事件(eventName),第二個參數綁定一個回調函數來接受數據。

//接收數據的組件
this.$bus.$on('eventName',(value)=>{
   console.log(value) //{a:1,b:2}
})

在數據發送組件內利用$ emit 提交給綁定的事件(eventName),後面的參數為要傳遞的數據;

//發送數據的組件
var obj = {a:1,b:2}
this.$emit('eventName',obj)

那既然Vue全局事件總線($ bus)能夠滿足任何組件的之間的通信,為什麼vue官方還要再創造出Vuex呢?

Vue全局事件總線

我們來看下面這個場景(利用事件總線進行數據共享)圖片來源於尚矽谷

上圖可以看到,A組件中data中有一個X屬性,B,C,D組件也都需要,共享屬性可以利用$ on 去獲取到X屬性;這樣看起來,感覺不是很簡單嗎,沒有什麼啊,別急,這隻是讀取屬性,那如果B,C,D需要修改呢?

其實隻需要在B,C,D組件內去利用$ emit事件把修改的值發送給A組件,A組件再通過$ on去接受然後對X屬性進行修改,光看文字是不是感覺已經很繞啦,也就是下圖所示:圖片來源於尚矽谷

紅色箭頭是B,C,D組件讀取到共享屬性X,綠色箭頭是B,C,D組件修改X屬性;

目前場景隻是展示四個組件都需要一個共享屬性X,通過讀寫,看上去都已經很亂啦,那如果大項目中有十幾個,幾十個組件都需要一個共享屬性X呢,豈不是更亂;

Vuex狀態管理

那如果要用Vuex實現X屬性的共享呢?看下圖:圖片來源於尚矽谷

Vuex是獨立於任何組件的一個存在,把A,B,C,D組件需要共享的屬性放到Vuex中去管理,不需要共享的屬性還是寫在原組件內;此時A,B,C,D組件和Vuex是雙向箭頭,就是組件既可以通過其內置的api去讀,也可以去修改,某一個組件修改共享的屬性,其他組件獲取的都是最新修改的數據;

何時使用Vuex

Vue事件總線其實也很方便,但是適合使用小項目中;對於大項目Vuex作為共享狀態集中式管理,是最好的選擇,方便使用以及維護;

那疑問來瞭,我也不清楚項目的大小怎麼辦,什麼時候適合使用Vuex呢?

  • 項目中多個組件都需要使用或修改共同一個狀態(多個組件共享同一個狀態)

二. 純vue組件案例

本來打算直接介紹引入Vuex的代碼步驟和方法,但是為瞭更好的理解好對比,我先把我寫的兩個組件案例demo和代碼給大傢看一下,稍後再給大傢看引入Vuex後的代碼,雖然功能都一模一樣,主要是對比Vuex使用前後的組件內部代碼不同;

計算總數案例

導航二的計算總數案例組件:

代碼如下:

<template>
    <div>
        <h3 style="marginBotton:10px;">此時導航三組件內總人數為:???</h3>
        <h3 :style="{marginBottom:'10px'}">當前計算的和為:{{count}}</h3> 
        <el-select v-model.number="value" size="mini"  :style="{width:'60px'}" placeholder="0">
            <el-option
            v-for="item in options"
            :key="item.value"
            :label="item.label"
            :value="item.value">
            </el-option>
        </el-select>
        <el-button size="mini" @click="jia">+</el-button>
        <el-button size="mini" @click="jian">-</el-button>
        <el-button size="mini" @click="oddJia">和為奇數 +</el-button>
        <el-button size="mini" @click="asyncJia">等1秒後 +</el-button>
    </div>
</template>
<script>
export default {
    data(){
        return{
            count:0,
            options: [
                {value: 1,label: '1'},
                ...
            ],
            value: null
            }
    },
    methods:{
         jia(){
           this.count += this.value
         },
         jian(){
           this.count -= this.value
           if(this.count <= 0){
               this.count = 0
           }
         },
         oddJia(){
             if(this.count%2 !== 0){
                this.jia()
             }
         },
         asyncJia(){
             setTimeout(() => {
                 this.jia()
             },1000)
         }
    }
}
</script>

添加人員案例

導航三添加人員案例組件:

代碼如下:

<template>
    <div>
         <el-select v-model="value" placeholder="請選擇添加人員">
            <el-option
            v-for="item in options"
            :key="item.name"
            :label="item.name"
            :value="item.name">
            </el-option>
        </el-select>
        <el-button  @click="addPerson">添加</el-button>
        <el-button  @click="rest">還原</el-button>
        <el-table
            :data="filterTable"
            :border='true'
            style="width: 100%;marginTop:10px">
            <el-table-column
                align="center"
                prop="name"
                label="姓名">
            </el-table-column>
            ...
        </el-table>
        <h3 style="marginTop:10px;">此時表格總人數為:{{filterTable.length}}</h3>
        <h3 style="marginTop:10px;">此時導航二組件的計算總和為:???</h3>
    </div>
</template>
<script>
export default {
    data() {
        return {
           value:'',
           options: [
                {name: '王朝', sex:'男',age:21,hobby:'武術'}, 
                ...
            ],
            tableData: [
                {name: '張三', sex:'男',age:18,hobby:'唱歌'}, 
                {name: '李四', sex:'女',age:20,hobby:'跳舞'},   
            ],
           filterTable:[]
        }
    },
    mounted(){
        this.filterTable.push(...this.tableData)
    },
    methods:{
        addPerson(){
            var isHave = true
            this.filterTable.forEach( e => {
                if(e.name == this.value)  isHave = false
            })
            if(!isHave){
                this.$message({
                    message: '人員已添加!',
                    type: 'warning',
                    duration:1000
                })
                return
            }

           var person =  this.options.filter( item => {
                return item.name == this.value
            })
            this.filterTable.push(person[0])
        },
        rest(){
           this.filterTable = this.tableData
        }
    }
}
</script>

此時兩個組件是完全獨立的,沒有實現數據共享,所以在用到對方組件內數據的地方以 ”???“ 標記;

接下來,我們就開始一步一步使用Vuex去實現兩個組件的數據共享;

三. Vuex工作原理和流程

下面是官方給的Vuex的工作原理圖,如下:

如果想要很熟悉的使用Vuex,那我們就應該先瞭解其工作原理,明白瞭它內部的運轉機制,那麼我們就可以告別死記,就可以很熟悉流暢的編寫代碼;

從上圖工作原理圖來看Vuex中有三個最重要的核心對象,Actions,Mutations,State,那他們三個是什麼關系,怎麼協助運轉呢,下面我們來看一下Vuex的工作流程;

第一種工作流程

為瞭方便瞭解,我給Vuex工作原理圖稍微做瞭一些標註,圖如下:

這個紅色箭頭就是組件使用Vuex的工作流程,配合上圖,認真理解下面文字,看一下組件是怎麼修改共享數據的:

  • vue組件內調用dispacth()函數,該函數內又兩個參數,第一個參數是一個方法名‘key1’,第二個參數是傳入的值
  • 由於的組件調用瞭dispatch,就會在Actions對象中去找dispatch函數傳入的一個參數‘key1’;
  • 找到key1後,就會調用對應的函數;key1函數內部又調用瞭commit()函數。commit()函數也有兩個參數,第一個參數是一個方法名‘KEY1’,第二個參數是傳入的值;
  • 由於調用瞭commit,就會在Mutations對象中去找commit函數傳入的第一個參數‘KEY1’;
  • 在Mutations對象找到‘KEY1’對應的函數後,Vuex內部調用Mutate對狀態就行修改,修改狀態後對修改狀態的組件進行render

第二種工作流程

現在應該瞭解其工作的大致流程瞭吧,別急,還沒有完,我們繼續看下圖:

這個應該是最完善的工作流程圖,除瞭剛才我們介紹的最外圈的工作流程外,其實組件也可以直接去調用commit()去修改狀態;那該怎麼區分和使用呢?

  • Vuex內部有規定,Actions中主要寫一些復雜的業務邏輯和異步處理,Mutations中主要修改狀態;
  • 如果在組件內就可以拿到需要傳遞的value值,內部不在需要對value進行過多的業務瞭邏輯處理,可以直接commit()去Mutations中調用修改狀態函數
  • 如果需要傳遞的value值要通過接口獲取,然後還要進行復雜的業務邏輯,最好放到Actions的函數去處理;
  • 這樣做的好處是Devtools開發工具可以準確的監控Vuex狀態的變化;

生活化的Vuex工作原理

以上就是Vuex的全部工作原理以及流程;但是為瞭讓大傢更好的去記住和理解,我又模擬瞭下面這個場景:

Vuex就相當於一個飯店,vue組件就是顧客,Actions就是服務員,Mutations就是廚師,state就是做出來的菜和飯;我們把修改共享狀態的任務改成點菜,我們可以走一下點餐流程:

通過服務員點餐的流程:

  • 首先,顧客因為不熟悉飯店菜品點菜的時候需要詢問,所以要找‘服務員1’點菜,或者是通過掃描桌上外賣平臺點餐
  • 其次,‘服務員1’整理好菜單後就交給瞭後廚‘廚師1’去做。後廚安裝的有監控,以後想要知道哪個廚師做的哪個顧客的菜都能查詢到;
  • 最後,廚師1做好菜,顧客就可以直接拿到;

顧客自己點餐的流程:

  • 首先,顧客很熟悉這傢飯店,菜系瞭解,廚師也都認識這樣就很簡單,直接找到‘廚師1’要瞭幾個菜;
  • 最後,廚師1做好菜,顧客就可以直接拿到;

現在應該很熟悉Vuex的工作流程瞭吧,很簡單,學會點菜就行,哈哈,那接下來我們就要開始使用Vuex啦;

四. 在項目中引入Vuex

安裝Vuex

首先,你的電腦要安裝node環境,使用npm安裝:

npm install vuex --save

創建store

然後在項目src文件夾下創建一個store文件夾,在其裡面創建index.js:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {},
  getters:{},
  mutations: {},
  actions:{},
  modules:{}
})
export default store

在Vue中掛載store

最後在main.js文件內引入創建的store:

import Vue from 'vue'
import App from './App'
import store from './store'

new Vue({
  el: '#app',
  components: { App },
  template: '<App/>',
  store
})

五. Vuex的核心屬性用法

知道一個組件怎麼使用,另一個就不在話下,這裡我以計算總數組件為例;

在創建store實例的時候,大傢可以看到Vuex內部有五個核心屬性,下面我們就從這五個核心屬性入手,一步一步實現計算總數案例的所有原有功能;

刪除掉計算總數組件所有邏輯代碼,此時demo,如下圖:

代碼如下:

<template>
    <div>
        <h3 style="marginBotton:10px;">此時導航三組件內總人數為:???</h3>
        <h3 :style="{marginBottom:'10px'}">當前計算的和為:???</h3> 
        <el-select v-model.number="value" size="mini"  :style="{width:'60px'}" placeholder="0">
            <el-option
            v-for="item in options"
            :key="item.value"
            :label="item.label"
            :value="item.value">
            </el-option>
        </el-select>
        <el-button size="mini" @click="jia">+</el-button>
        <el-button size="mini" @click="jian">-</el-button>
        <el-button size="mini" @click="oddJia">和為奇數 +</el-button>
        <el-button size="mini" @click="asyncJia">等1秒後 +</el-button>
    </div>
</template>
<script>
export default {
    data(){
        return{  
            options: [
                {value: 1,label: '1'},
                ...
            ],
            value: null
            }
    },
    methods:{
         jia(){},
         jian(){},
         oddJia(){},
         asyncJia(){ }
    }
}
</script>

單一數據源(state)

首先是state配置,它的值是一個對象,用來存儲共享狀態;Vuex使用單一樹原則,將所有的狀態都放到這個對象上,便於定位和維護;

我們把計算總數組件的count放到Vuex中的state中,如下:

...
const store =  new Vuex.Store({
  state: {
    count:0
  }, 
})
...

組件中獲取,可以這樣:

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

如果組件內需要引入的共享狀態屬性較多,都使用this.$store.state.x的話,會比較麻煩,所Vuex給我們提供瞭mapState()輔助函數來獲取,如下:

import {mapState} from 'vuex'
 ...
 computed:{
  //參數為對象寫法
   ...mapState({
     //key值隨意自定義,模板中插值也要寫入自定義的key值
       count:'count' 
   })
   //參數為數組寫法
   // 此時組件定義的屬性名要和state中定義的屬性名一致
  ...mapState(['count'])
  },
  ...

以上兩種獲取方式,組件都可以獲取到共享的count屬性,並顯示到頁面上,如下圖:

狀態更新方式(mutations)

Vuex中的狀態和組件中的狀態不同,不能直接 state.count = ‘xx’ 這種方式去修改;Vuex修改狀態的唯一方式就是提交mutation;

mutation是一個函數,第一個參數為state,它的作用就是更改state的狀態;

下面我們就來定義一個mutation,在函數內去更新count這個狀態:

const store =  new Vuex.Store({
  state: {
    count:0
  },
  mutations: {
    //更改count方法,type為增加
    JIA(state,count){
      state.count += count
    }
  }
})

組件中提交commit函數去觸發mutattions中定義的JIA函數:

...
 methods:{
    jia(){
         this.$store.commit('JIA',this.value)
     }
 }
 ...

或者是利用最常用的mapMutations輔助函數,如下:

...
 //把獲取的單選框的value值,通過參數傳給mutation定義的jia函數,
 <el-button size="mini" @click="JIA(value)">+</el-button>
 ...
 import {mapMutations} from 'vuex'
 ...
  methods:{
     //對象參數中的key值為自定義的點擊事件名
      ...mapMutations({JIA:'JIA'}),
      //參數為數組,此時點擊事件名要和mutation中定義的增加事件名一致
       ...mapMutations(['JIA',]),
  }

以上兩種方法都能實現增加功能,如下圖:

store中的計算屬性(getters)

有時候我們需要從 store 中的 state 中派生出一些狀態,例如對列表進行過濾並計數,這裡我們就簡單的把計算總數和隨之增加10倍:

Getter 接受 state 作為其第一個參數,如下:

const store =  new Vuex.Store({
  state: {
    count:0
  },
  getters:{
    multipleCount(state){
      return state.count * 10
    }
  }
})

組件中可以這樣去獲取,如下:

<h3>當前計算的和的10倍為:{{multipleCount }}</h3> 
...
 computed:{
    multipleCount(){
        return  this.$store.getters.multipleCount
    },
 },

或者利用輔助函數mapGetters獲取,和mapState引入一樣,如下:

import {mapGetters} from 'vuex'
...
computed:{
    //對象寫法
    ...mapGetters({
        multipleCount: 'multipleCount'
    }),
    
    //數組寫法
    //此時自定義的函數名要和getter中定義的函數名一致
   ...mapGetters(['multipleCount']),
 },

以上兩種方式都可以獲取到getter函數,如下圖:

異步更新狀態(actions)

Action 類似於 mutation,不同在於:

  • Action 提交的是 mutation,而不是直接變更狀態。
  • Action 可以包含任意異步操作。

最大的區別就是action中可以寫一些業務邏輯和異步操作;而mutation就是簡單的變更狀態;

怎麼說呢,其實action很像一個連接組件和mutation的中介,對於同步和異步操作在action中都能完成,它隻是幫我們在actions函數中commit提交瞭mutations中的函數;

同步增加總數

下面我們來再一次用action來實現點擊JIA函數增加總數(同步操作),如下:

const store = new Vuex.Store({<!--{C}%3C!%2D%2D%20%2D%2D%3E--> state: {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> count:0 }, mutations: {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> JIA(state,count){<!--{C}%3C!%2D%2D%20%2D%2D%3E--> state.count += count } }, actions:{<!--{C}%3C!%2D%2D%20%2D%2D%3E--> //第一個參數為context(上下文),第二個參數為傳的值 jia(context,value){<!--{C}%3C!%2D%2D%20%2D%2D%3E--> console.log(context) //打印結果如下圖 context.commit('JIA',value) } },})

這就是 console.log(context) 的結果,是一個對象,裡面包含瞭commit,dispatch,getters等方法,像一個小型的srore,但是跟store不一樣,我們這裡就叫上下文;

既然context是個對象,如果我們隻需要commit的話,也可以使用數據解構的方式寫,入下圖:

 actions:{
   //第一個參數為context(上下文),第二個參數為傳的值
    jia({commit},value){
        commit('JIA',value)
    }
  },

組件可以分發action,通過 store.dispatch 方法觸發:

methods:{   
    jia(){
          this.$store.dispatch('jia', this.value)
      },
}

或者通過mapActions輔助函數來觸發:

import { mapActions} from 'vuex'
...
methods:{   
   //對象參數中的key值為自定義的點擊事件名
   ...mapActions({jia:'jia'}), 
   //數組參數
   //此時點擊事件名要和action中定義的增加事件名一致
   ...mapActions(['jia']), 
}

我們利用actions和mutations都可以實現JIA函數的功能使總數增加;那問題來瞭,我們可以調用mutations裡面定義的JIA函數,為什麼還要多此一舉的在actions中去調用mutation,然後再去分發actions呢;

當然這裡我們隻是演示給大傢作為參考,一般同步操作我們也不會多此一舉的放入actions裡面,項目中直接更新狀態我們就直接在組件commit提交mutation函數就可以;

異步增加總數

由於項目沒有寫後臺服務,是mock的數據,這裡我們異步就用定時器來寫,如下圖:

 actions:{
    asyncJia({commit},value){
        setTimeout(() => {
          context.commit('JIA',value)
        },1000) 
    }
 },

組件中分發action方法如下:

 methods:{
       asyncJia(){
           this.$store.dispatch('asyncJia',this.value)
        }
  }

一樣,也可以利用mapActions函數,如下:

import { mapActions} from 'vuex'
...
methods:{   
   // //對象參數中的key值為自定義的點擊事件名
   ...mapActions({asyncJia:'asyncJia'}), 
    //數組參數,此時點擊事件名要和action中定義的增加事件名一致
   ...mapActions(['asyncJia']), 
}

不管是同步和異步操作,以上方法都能實現增加總數的功能;

為什麼actions中處理異步

那問題來瞭,為什麼actions中可以處理異步操作,而不能在mutations中進行呢?

以上屬於簡單的一些業務邏輯,action中的定時器或者奇數判斷其實在mutation中就可以完成,這樣感覺action就像是多餘的一樣;

官方明確表示:

  • mutations裡同步的意義在於,每一個mutation執行完畢之後,可得到對應的狀態,方便Vuex的devtools工具可以跟蹤狀態的變化
  • 如果在mutations中寫入異步,devtools工具就沒法知道狀態是什麼時候更新的,所以才有瞭actions
  • actions用來專門處理異步,裡面觸發mutations,就可以很清楚的看到mutation是何時被記錄下來的,並且可以立即查看對應的狀態,這樣異步更新也可以看到更新狀態的流程;

舉個例子,還有就是遇到一些復雜的業務邏輯,第二個異步操作可能會依賴第一個操作的結果;

代碼如下:

actions:{
    asyncJia({commit},value){
      return new Promise((resolve,reject) => {
        setTimeout(() => {
          context.commit('JIA',value)
          resolve()
        },1000)
      })  
    }
 },

組件內調用此異步增加函數後,返回結果後再等1秒調用增加函數,代碼如下:

...
<el-button size="mini" @click="doubleAsync(value)">異步 + 後再等1秒後 +</el-button>
...
import {mapMutations, mapActions} from 'vuex'
...
methods:{
     ...mapMutations(['JIA','JIAN']),
      ...mapActions(['oddJia','asyncJia']),
      doubleAsync(){
          this.asyncJia(this.value).then(() => {
            setTimeout(() => {
               this.JIA(this.value)
            },1000)
          })
      }
  }

demo案例如下圖:

分工明確,這樣看著清晰明瞭,操作起來也很方便;

擴展:其實在matations中也可以實現異步操作,或者不需要mutations,直接在action中去更改state狀態,頁面功能也都可以正常使用並顯示;但是上面說到瞭,如果Vuex沒有操作規則,大傢都隨心所欲的去編寫邏輯代碼,devtools工具也不會正確跟蹤,那樣也不便於後期維護,代碼一樣很亂,所以我們還是聽從Vuex官方推薦的標準去使用;

還有一個modules模塊沒有介紹,不要著急,目前我們隻是實現瞭單個組件與Vuex關聯,隻是介紹瞭核心屬性的api用法,還沒有實現多組件數據共享;

下面就把項目中兩個組件的詳細的Vuex版本代碼分享出來,作為參考。

六. 使用Vuex組件的完整代碼

store倉庫代碼

import Vue from 'vue'
import Vuex from 'vuex'
import {message} from 'element-ui'
Vue.use(Vuex)

const store =  new Vuex.Store({
  state: {
    count:0,
    filterTable:[]
  },

  getters:{
    multipleCount(state){
      return state.count * 10
    }
  },

  mutations: {
    JIA(state,count){
      state.count += count
    },
    JIAN(state,count){
      state.count -= count
      if(state.count <= 0) state.count = 0
    },
    ADDPERSON(state,person){
      state.filterTable.push(...person)
    },
     //這個重置方法本不需要,隻是為瞭組件使用commit
     //dispatch('restPerson')就可以實現,這裡為瞭下面講解知識點而用
    RESTPERSON(state,person){
        state.filterTable = []
        state.filterTable.push(...person)
     }
  },

  actions:{
    asyncJia(context,value){
      return new Promise((resolve,reject) => {
        setTimeout(() => {
          context.commit('JIA',value)
          resolve()
        },1000)
      })  
    },
    oddJia({commit,state},value){
       if(state.count%2 !== 0){
        commit('JIA',value)
       }
    },
    addPerson({commit,state},obj){
      var isHave = true
      state.filterTable.forEach( e => {
          if(e.name == obj.value)  isHave = false
      })
      if(!isHave){
          message({
              message: '人員已添加!',
              type: 'warning',
              duration:1000
          })
          return
      }
      
      var person =  obj.options.filter( item => {
        return item.name == obj.value
      })
      commit('ADDPERSON',person)
    },
    restPerson({commit,state},value){
       state.filterTable = []
       commit('ADDPERSON',value)
    }
  },
})

export default store

計算總數案例

導航二計算總數案例組件代碼,如下:

<template>
    <div>
        <h3 style="marginBotton:10px;">此時導航三組件內總人數為:???</h3>
        <h3>當前計算的和為:{{count}}</h3> 
        <h3 :style="{marginBottom:'10px'}">當前計算的和的10倍為:{{multipleCount }}</h3> 
        <el-select v-model.number="value" size="mini"  :style="{width:'60px'}" placeholder="0">
           ...
        </el-select>
        <el-button size="mini" @click="JIA(value)">+</el-button>
        <el-button size="mini" @click="JIAN(value)">-</el-button>
        <el-button size="mini" @click="oddJia(value)">和為奇數 +</el-button>
        <el-button size="mini" @click="asyncJia(value)">等1秒後 +</el-button>
        <el-button size="mini" @click="doubleAsync(value)">異步 + 後再等1秒後 +</el-button>
    </div>
</template>
<script>
import {mapState,mapGetters,mapMutations, mapActions} from 'vuex'
export default {
    data(){
        return{  
            options: [
                {value: 1,label: '1'},
                ...
            ],
            value: null
            }
    },
    computed:{
        ...mapGetters(['multipleCount']),
        ...mapState(['count'])
    },
    methods:{
        ...mapMutations(['JIA','JIAN']),
        ...mapActions(['oddJia','asyncJia']),
        doubleAsync(){
            this.asyncJia(this.value).then(() => {
              setTimeout(() => {
                 this.JIA(this.value)
              },1000)
            })
        }
    }
}
</script>

添加人員案例

導航三添加人員案例代碼,如下:

<template>
    <div>
         <el-select v-model="value" placeholder="請選擇添加人員">
            <el-option
            v-for="item in options"
            :key="item.name"
            :label="item.name"
            :value="item.name">
            </el-option>
        </el-select>
        <el-button  @click="addPerson">添加</el-button>
        <el-button  @click="rest">還原</el-button>
        <el-table
            :data="filterTable"
            :border='true'
            style="width: 100%;marginTop:10px">
             ... 
            </el-table-column>
        </el-table>
        <h3 style="marginTop:10px;">此時表格總人數為:{{filterTable.length}}</h3>
        <h3 style="marginTop:10px;">此時導航二組件的計算總和為:???</h3>
    </div>
</template>
<script>
export default {
    data() {
        return {
           value:'',
           options: [...],
           tableData: [...],
        }
    },
    computed:{
        filterTable(){
          return this.$store.state.filterTable
       },
    },
    mounted(){
       this.initTable()
    },
    methods:{
        initTable(){
            const person = this.filterTable.length == 0 ? this.tableData  :  this.filterTable
             this.$store.commit('RESTPERSON',person)
        },
        addPerson(){
            const obj = {
                value:this.value,
                options:this.options
            }
            this.$store.dispatch('addPerson',obj)
        },
        rest(){
           this.$store.dispatch('restPerson',this.tableData)
        }
    }
}
</script>

Vuex實現組件間數據共享

現在就算我不去介紹,大傢也會用瞭,現在兩個組件所需彼此的數據已經放到瞭倉庫的state中,隻需要直接獲取就行;

那我們就把兩個組件一直存在的 ”???“ 給他們獲取一下值;

計算總數組件中獲取添加人員組件中表格的人員總數,如下:

   ...
   <h3 style="marginBotton:10px;">
      此時導航三組件內總人數為:{{filterTable.length}}
   </h3>
   <h3>當前計算的和為:{{count}}</h3> 
   ...
    computed:{
        //隻需要引入state中的filterTable屬性即可
        ...mapState(['count','filterTable'])
    },
   ...

添加人員組件中獲取計算總數的值,如下:

 ...
 <h3 style="marginTop:10px;">
     此時表格總人數為:{{filterTable.length}}
 </h3>
 <h3 style="marginTop:10px;">此時導航二組件的計算總和為:{{count}}</h3>
  ...
    computed:{
      filterTable(){
          return this.$store.state.filterTable
       },
       count(){
          return this.$store.state.count
       },
    },
  ...

隻要導航二計算組件中的計算總數值count發生變化,導航三添加人員組件內的計算總和也隨之變化;

隻要導航三添加人員組件表格數據發生變化,導航二計算總數組件內總人數也隨之變化;如下圖:

這樣就是實現瞭組件間數據的共享,簡單多瞭吧,你也可以像我這樣去練習;

七. Vuex模塊化編碼(modules)

其實到目前為止,你已經基本上學會瞭Vuex的使用瞭,modules隻是輔助我們把Vuex模塊化,讓我們的代碼更清晰明瞭,便於維護和開發,提高發開效率;

想一下,目前我們隻是有兩個組件需要共享數組,看著沒有問題,同樣要是有十幾個組件,幾十個組件也需要共享數據呢,難道我們要把所有共享的狀態和更改的業務邏輯都寫在一個state,mutations和actions中嗎,可以想到肯定很亂,怎麼辦,這就有瞭modules;

store模塊化

如果要使用模塊化編碼的話,store中的代碼編寫會發生變化,組件引入的api不會沒有改變,但是寫法也會有所不同;

首先我們先來使store分成模塊,目前我們有兩個組件,每個組件的state,mutations和actions都是獨立的,所以我們可以拆分兩個模塊:

在store文件夾下創建一個count.js文件,此文件隻屬於計算總數的狀態和業務邏輯,最後記得暴漏出去,如下:

const countOptions = {
   //這裡添加此屬性,是為瞭後面使用map輔助函數簡寫時可以找到該模塊
    namespaced: true,
    state: {
        count:0,
    },
    getters:{
      multipleCount(state){
         return state.count * 10
      }
    },
    mutations: {
      JIA(state,count){
          state.count += count
      },
      JIAN(state,count){
	      state.count -= count
	      if(state.count <= 0) state.count = 0
      },
    },

    actions:{
      asyncJia(context,value){
	      return new Promise((resolve,reject) => {
	          setTimeout(() => {
	            context.commit('JIA',value)
	            resolve()
	          },1000)
	      })  
      },
      oddJia({commit,state},value){
	      if(state.count%2 !== 0){
	          commit('JIA',value)
	       }
      },
    },
}
export default countOptions

在store文件夾下創建一個addPerson.js文件,此文件隻屬於添加人員的狀態和業務邏輯,最後也要暴露出去,如下:

import {message} from 'element-ui'
const addPersonOption = {
    namespaced: true,
    state: {
        filterTable:[]
    },
    mutations: {
      ADDPERSON(state,person){
          state.filterTable.push(...person)
      },
     //這個重置方法本不需要,隻是為瞭組件使用commit
     //dispatch('restPerson')就可以實現,這裡為瞭下面講解知識點而用
      RESTPERSON(state,person){
        state.filterTable = []
        state.filterTable.push(...person)
      }
    },
    actions:{
      addPerson({commit,state},obj){
	       var isHave = true
	       state.filterTable.forEach( e => {
	           if(e.name == obj.value)  isHave = false
	       })
	       if(!isHave){
	           message({
	               message: '人員已添加!',
	               type: 'warning',
	               duration:1000
	           })
	           return
	       }  
	       var person =  obj.options.filter( item => {
	         return item.name == obj.value
	       })
	       commit('ADDPERSON',person)
      },
      restPerson({commit,state},value){
	        state.filterTable = []
	        commit('ADDPERSON',value)
      }
    },
}
export default addPersonOption

在store/index.js文件裡面引入上面兩個模塊組件,掛到如下modules中,如下:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

import countOptions  from './count'
import addPersonOption from './addPerson'

const store =  new Vuex.Store({
  modules:{
    countOptions,
    addPersonOption
  }
})
export default store

這樣分成模塊去開發,是不是,眼前一亮,清晰多啦;

模塊化的組件調用

看到這裡不知道你是否發現,我在計算總數組件裡面用的都是map函數調用方法;在添加人員組件用的都是this.$store.xx的調用方法;之所以這樣做就是為瞭現在使用模塊化的時候給大傢都全部介紹一下api在模塊下的引入方式;

map輔助函數

命名空間(namespaced)+ map輔助函數的簡單寫法去調用,寫法如下;

mapState函數的調用改為:

    computed:{
        ...mapState('countOptions',['count']),
        ...mapState('addPersonOption',['filterTable'])
    },

mapGetters函數的調用改為:

    computed:{
        ...mapGetters('countOptions',['multipleCount']),
    },

mapMutations函數的調用改為:

methods:{
     ...mapMutations('countOptions',['JIA','JIAN']),
 }

mapActions函數的調用改為:

methods:{
    ...mapActions('countOptions',['oddJia','asyncJia']),
 }

this.$store.xxx

獲取模塊化states方法,state後面加上模塊名,如下:

computed:{
   filterTable(){
      return this.$store.state.addPersonOption.filterTable
   },
   count(){
      return this.$store.state.countOptions.count
   },
},

獲取模塊化mutations方法,在mutation函數名前加上 “模塊名+/”,如下:

   methods:{
       initTable(){
            var person = this.filterTable.length == 0 ? this.tableData  :  this.filterTable
            this.$store.commit('addPersonOption/RESTPERSON',person)
        },
   }

為瞭瞭解獲取模塊化getters的調用方法,我們給Vuex的addPersonOption模塊添加一個getters方法獲取表格第一個人的名字,組件調用展示,代碼如下:

 getters: {
      onePersonName(state){
         return state.filterTable[0].name
      }
 },
 computed:{
       onePersonName(){
           return this.$store.getters.['addPersonOption/onePersonName']
       }
  }

獲取模塊化actions方法,如下:

 methods:{
     addPerson(){
          var obj = {
              value:this.value,
              options:this.options
          }
          this.$store.dispatch('addPersonOption/addPerson',obj)
      },
      rest(){
          this.$store.dispatch('addPersonOption/restPerson',this.tableData)
      }
 }

再給大傢看一下demo,修改後,功能正常使用!完美!,圖如下:

八. 總結

好瞭,以上就是關於Vuex的所有知識點,因為最近又刷瞭一遍尚矽谷Vue的視頻,為瞭練習和總結,就寫瞭這篇將近18000字的關於Vuex博客,應該算是很詳細,很詳細啦!

到此這篇關於一篇文章帶你吃透Vuex3的狀態管理的文章就介紹到這瞭,更多相關Vuex3狀態管理內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: