Vue-6 Vuex(state, getters, mutations, actions, modules)

Vue-6 Vuex(state, getters, mutations, actions, modules)

在項目研發過程中,組件和組件之間的結構關係非常複雜,到處存在數據交互。 props$emit,路由傳參是遠遠不足以支撐複雜的數據交互。

Vue 單獨提供了 vuex 來做項目的狀態管理,vuex 提供了公共數據的存儲和操作方式,讓組件之間的數據交互變得更簡潔。

vuex 的核心理念就是存儲一些供各個組件使用的數據,無論組件的嵌套結構如何,都能訪問 vuex 中的數據。任何一個組件對其中的數據做了修改,在其他組件中的渲染會立即響應。

一、Vuex的安裝和基本配置

vue-router 一樣,要在項目中使用 vuex,需要先下載依賴包。

npm i vuex --save

安裝成功之後在 src 文件夾中創建項目目錄:

src
    └── App.vue
    └── main.js
    └── assets
    └── components
           // ...
    └── router
           // ...
    └── store // 狀態管理庫的目錄
           index.js // 狀態管理庫的根配置文件
            

vuex 的基本配置:

// store>index.js
import Vue from 'vue'
import Vuex from 'vuex' // 引入 vuex

Vue.use(Vuex); 

// 創建一個狀態管理庫實例並且導出,以備使用
export default new Vuex.Store({
    // code
});

配置好了之後要將這個 Vuex.Store 實例對象傳入根組件的實例化中,整個項目才能使用 vuex

// main.js
import Vue from 'vue'
import App from './App'

// 導入 store (省略)
import store from './vuex'

Vue.comfig.productionTip = false;

new Vue({
    el: '#app',
    // 將 store 傳入根組件實例化的參數中
    store,
    components: { App },
    template: '<App/>'
});

vuexStore 類提供了五個配置,包含了可能出現的各種操作。

二、state

state 中存放的是 store 中所有的數據列表。

任何組件無論嵌套關係多複雜,都可以訪問 state 中的數據。

// store>index.js 中的 state
state: {
   num: 100,
   bol: true
}

在任意組件中獲取:

<!-- Any.vue -->
<template>
   <div class="any">
       <h2>num: {{ num }}</h2>
       <h2>bol: {{ bol }}</h2>
   </div>
</template>
<script>
   export default {
       name: 'Any',
       computed: {
          // 建議將對於 store 中的數據的獲取或者操作寫在計算屬性中,這樣當 store 中的數據變化時,也能及時響應
          num() {
              return this.$store.state.num;
          },
          bol() {
              return this.$store.state.bol;
          }
       }
   }
</script>

在其他任何組件中,對 state 中的數據做操作時,當前組件也會隨之響應。

store 的好處之一就是無論組件和組件之間的嵌套結構是怎樣的,都能共同訪問 store 中的數據。

但是如果只是想訪問 state 中的數據,且訪問了很多個數據,那麼爲每一個數據單獨的寫一個計算屬性,就顯得很不理智。

vuex 提供 mapState 來批量的訪問 state 中的數據

<!-- Any.vue -->
<script>
   import { mapState } from 'vuex';
   export default {
       computed: {
           ...mapState([
              'num',
              'bol'
           ]);
       }
   }
</script>

調用方式不變。

三、getters

給一個示例,state 中有一個數組,數組中有各種類型的數據,有兩個組件(A和B),都只想要這個數組中的 number 列表。

state: {
   arr: [1, 4, 5, 'hello', true, 'world', 98]
}

我們可以在 A 和 B 的計算屬性中先拿到 state 中的 arr,對其做處理,返回數字列表:

<!-- A.vue -->
<script>
   export default {
       name: 'A',
       computed: {
          numArr() {
              const arr = this.$store.state.arr;
              return arr.filter(function(item) {
                   if(typeof item == 'number') return item;
              });
          }
       }
   }
</script>

如果 B 組件的需求和 A 組件一樣,那麼 numArr 中的這段代碼,在 B 組件中,又要重新寫一遍。

這樣就形成了代碼冗餘。

所以 vuex 提供了 getters,來根據代碼需求過濾 state 中的數據,向外拋出過濾之後的數據。

// store>index.js 中導出的實例 
export default new Vux.Store({
    state: {
       arr: [1, 4, 5, 'hello', true, 'world', 98]
    },
    getters: {
        // getters 中的函數接收的第一個參數就是 state,可以通過它直接訪問數據列表
        numArr: state => {
            const arr = state.arr;
            return arr.filter(function(item) {
                if(typeof item == 'number') return item;
            });
        }
    }
});

現在 A 、B 組件想要訪問 state 中的 arr 中的數字列表,可以直接訪問 getters

<!-- A.vue -->
<script>
    export default {
        name: 'A',
        computed: {
            // 求 state 中的 arr 中的所有數字的和
            numSum() {
                const arr = this.$store.getters.numArr;
                // 求和代碼
            }
        }
    }
</script>
<!-- B.vue -->
<script>
    export default {
        name: 'B',
        computed: {
            // 求 state 中的 arr 中的所有數字的最大值和最小值
            numSum() {
                const arr = this.$store.getters.numArr;
                const min = Math.min(...arr);
                const max = Math.max(...arr);
            }
        }
    }
</script>

可以認爲 gettersstore 的計算屬性,當 state 中的數據變化的時候,getters 返回的數據也會隨之變化,且只有當它的依賴值發生了變化之後纔會變化。

如果需求再變化:A 組件希望拿到 arr 中所有的數字,B 組件希望拿到 arr 中所有的字符串。那可能需要向 getters 中傳遞想要的數據類型,實現按需獲取數據的效果。

所以我們也可以以函數的形式訪問 getters,並且向其傳參。

// store 中的 getters
getters: {
    getArrByType(state) {
        return function(type) {
            const arr = state.arr;
            return arr.filter(function(item) {
                if(typeof item == type) return item;
            });
        }
    }
    /* 
    關於以函數的形式訪問getters官網給出的是箭頭函數簡寫形式(兩種意義一樣):
    getArrByType: (state) => (type) => {
        const arr = state.arr;
        return arr.filter(function(item) {
            if(typeof item == type) return item;
        });
    }
    */
}

訪問:

// 任意組件的 computed
computed: {
    getData() {
        const numArr = this.$store.getters.getArrByType('number');
        const strArr = this.$store.getters.getArrByType('string');
    }
}

同樣的,如果 getters 只是獲取而不傳參,也不用爲其寫很多個計算屬性。

vuex 提供 mapGetters 來批量獲取 getters

// 任意組件的 js
import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用對象展開運算符將 getter 混入 computed 對象中
    ...mapGetters([
      'getData1',
      'getData2',
      // ...
    ])
  }
}

調用時直接將這些數組中的參數作爲變量名去使用即可。
如果覺得 store 中對於 getters 的命名太冗長,也可以在某個組件中爲其單獨命名

// 在當前組件中,gd 就代表 getArrByType
mapGetters({
    gd: 'getArrByType'
})

四、mutations

根據代碼的需求,有時候我們可能也需要對 vuex 中的數據做一些更改。

vuex 規定,只能在其提供的 mutations 中修改 state 中的數據,否則嚴格模式下會直接報錯。

// store>index.js 中 store 實例的導出
export default new Vuex.Store({
    state: {
        arr: [1,4,6]
    },
    mutations: {
        // 向 arr 中添加數據
        arrPush(state) {
            // mutations 中接收的函數的第一個參數也是 state
            state.arr.push(Math.floor(Math.random() * 100));
        }
    }
});

在任意組件中調用 mutations 中的函數需要使用到 storecommit

// 任意組件的 created
created() {
    // 調用 store 的 mutations 中的 arrPush
    this.$store.commit('arrPush');
}

也可以向 mutations 中的函數傳參

mutations: {
    arrPush(state, data) {
        // data 就是在調用 mutations 時傳遞過來的參數
        state.arr.push(data);
    }
}

調用:

// 任意組件的 created
created() {
    this.$store.commit('arrPush', Math.random());
}

vuex 建議: 向 mutations中傳遞的參數儘量是一個對象,這樣便於傳輸大量數據。

需要注意的是:mutations 中不能存在異步操作。

我們也可以使用 mapMutations 將組件中的 methods 映射成 mutations 中的函數。

methods: {
    ...mapMutations([
       // 在當前組件中,ap 就代表 arrPush  
       ap: 'arrPush'
    ])
  }

五、actions

vuex 不允許 mutations 中出現異步操作,那麼有個異步操作剛好需要對 state 做改變,就需要寫在 vuex 提供的 actions 中。

所以應該是在 actions 中寫異步代碼,在異步的某個過程中,如果需要對 state 做操作,就調用 mutations 中的函數。

// store>index.js 中的導出
export default new Vuex.Store({
    state: '500 miles',
    mutations: {
        setStr(state, newStr) {
            state.str = newStr;
        }
    },
    actions: {
        _setStr(context) {
            // Action 函數接受一個與 store 實例具有相同方法和屬性的 context 對象
            setTimeout(() => {
                context.commit('setStr', '500 miles away from home');
            });
        }
    }
});

調用:

// 任意組件的 created
created() {
    this.$store.dispatch('setStr');
}

actions 也可以接收 dispatch 時被傳遞的數據:

actions: {
    // 可以通過解構的方式從 context 中解構到 commit
    _setStr({commit}, newStr) {        
        setTimeout(() => {
            // newStr 就是調用 actions 時傳遞的參數
            context.commit('setStr', newStr);
        });
    }  
}

調用:

// 任意組件的 created
created() {
    this.$store.dispatch('setStr', '500 miles away from home');
}

六、modules

如果項目對於 state 的依賴很強,使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常複雜時,store 對象就有可能變得相當臃腫。

Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊。

將不同的需求的狀態單獨的聲明成一個變量,然後將其導入到根對象的 modules 中,就能生效。

// store>index.js
import Vue from 'vue'
import Vuex from 'vuex' 

Vue.use(Vuex); 

const moduleA = {
    state: { 
       msg: 'data of a' 
    },
    mutations: { ... },
    actions: { ... },
    getters: { ... }
}

const moduleB = {
    state: { 
        msg: 'data of b' 
    },
    mutations: { ... },
    actions: { ... }
}

const store = new Vuex.Store({
    state: {
        msg: 'data of root'
    }
    modules: {
        a: moduleA,
        b: moduleB
    }
});

// 創建一個狀態管理庫實例並且導出,以備使用
export default store

對於不同模塊的 state 中的數據的訪問:

// 任意組件的 created
created() {
    const data1 = this.$store.state.msg;
    const data2 = this.$store.state.a.msg;
    const data3 = this.$store.state.b.msg;
}

除了state之外,getters、mutations、actions的訪問方式不變。

如果要在子模塊的 getters 中訪問根 state 中的數據:

// 任意子模塊的 getters
getters: {
    getData(state, getters, rootState) {
        // state: 當前模塊的 state
        // rootState: 根模塊的 state
    }
}

如果要在子模塊的 actions 中訪問:

// 子模塊的actions
actions: {
    fn(context) {
        // context.state: 當前模塊的 state
        // context.rootState: 根模塊的 state
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章