1. 程式人生 > >vuex 使用詳解

vuex 使用詳解

Vuex 使用詳解

說明

Vuex 是一個專門為 Vue 應用程式開發的狀態管理模式,適合開發大型單頁應用, 它能夠更好的在元件外部管理狀態

重要檔案版本

  • “vue”: “^2.5.2”,
  • “vuex”: “^3.0.0”

特點

每一個 Vuex 應用的和興就是 store(倉庫)。store 基本上就是一個容器,它包含著應用中大部分的狀態(state) Vuex 和單純的全域性物件有以下不同

  1. Vuex的狀態儲存是響應式的,當 Vue 元件從 store 中讀取狀態時,若store中的狀態發生變化,那麼相應的元件也會得到高效更新
  2. 不能直接改變 store 中的狀態,改變 store 中狀態的唯一途徑就是顯式的提交(commit)mutation 這樣可以更方便的跟蹤狀態的變化

1. 安裝及引入

1. 下載

    npm install --save vuex

2. 安裝

    // 配置 store 
    import Vue from 'vue';
    import Vuex from 'vuex';

    // 通過 Vue.use() 安裝 Vuex
    Vue.use(Vuex);

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

    export default store;

    // main.js 中引入 store
    import store from './store/index'
; new Vue({ el: '#app', store, // 將 store 例項從根元件中‘注入’到每一個子元件中,子元件通過 `this.$store` 呼叫 template: '<app/>', components: { App } });

2. API 詳解

一個完整的 store 配置

    const store = new Vuex.Store({
        state: {},
        getters: {}, 
        mutations: {}, 
        actions: {},
        modules: {
        home: {
            state: {},
            getters: {}, 
            mutations: {}, 
            actions: {},
        }
        },
        strict: process.env.NODE_ENV !== 'production'
});

1. state

Vuex 中使用單一狀態樹,包含了全部應用層級狀態,每個應用將僅僅包含一個 store 例項

在 Vue 元件中獲得 Vuex 狀態

    // 根元件中注入
    import store from './store.js';

    const vm = new Vue({
        el: '#app',
        store, // store 例項注入
    })

    // Vue 元件中使用
    const  Child = {
        template: `<div>this is {{count}}</div>`,
        computed: {
            count() {
                return this.$store.state.count;
            }
        }
    }

2. mapState 輔助函式

為了解決 元件在獲取多個狀態的時候,頻繁宣告為計算屬性有些重複和冗餘,可以使用 mapState 輔助函式幫助生成計算屬性

    import { mapState } from 'vuex';
    import HomeBasic from '../components/Home/HomeBasic';

    export default {
       name: 'Home',
       data () {
          return {};
       },
       components: { HomeBasic },
       computed: {
          step() { //  普通計算屬性
             return 3;
          },
          ...mapState({ // 從 state 中的到的計算屬性
             count: state => state.global,// 可以是一個函式
             count: 'global', // 可以是一個字串,等同於 ‘state => state.global’
             count(state) { // 可以計算
                return state.global - this.step;
             }
          })
       },
    };

3. getter

state 中一般存放的都是比較原始的資料,我們的應用中使用的可能會是這些原始的資料經過計算後的資料,這時候我們可以在 元件的計算屬性中操作

   computed: {
      ...mapState({
         count(state) {
            return state.global * 100;
         }
      })
   },

如果有多個元件都需要使用這種計算後的屬性,這個時候可以使用 getter ,一種類似 Vue 元件中計算屬性的方式,同樣 getter 的返回值會根據它的依賴被快取起來,且只有當它的依賴值發生了改變才會被重新計算。

    // 定義 getter
    const store = new Vuex.Store({
        state: {
            global: 100,
            step: 3
        },
        getters: {
           count(state) { // 第一個引數是 state 
              return state.global * 200;
           },
           changeCount(state, getters) { // 可以傳入兩個引數 第二個引數是當前的 getter 
              return getters.count - state.step;
           },
           addCount: (state, getters) => (num) => { // 值可以是一個函式,用於傳遞引數
              return getters.count - state.step + num;
           }
        }, 
    });

    // 元件中使用
    computed: {
        count() {
                return this.$store.getters.count;
            },
            count2() {
                return this.$store.getters.changeCount;
            },
            count3() {
                return this.$store.getters.addCount(10); // 傳參
            },
        ...mapState({
        })
    },

4. mapGetters 輔助函式

與 mapState() 類似 mapGetters 輔助函式僅僅是將 store 中的 getter 對映到區域性計算屬性

   computed: {
      ...mapGetters([ // 引數為陣列時,可以將同名的屬性對映
         'count' 
      ]),
      ...mapGetters({ // 引數為物件時可以設定對映的名稱
         count2: 'changeCount'
      })
   },

5. mutation

以上 state、getter 都是用來獲取 store 中狀態的,而更改 store 中狀態只能通過提交 mutation ,每個 mutation 都由一個事件型別(type)和一個事件處理函式(handler),事件處理函式接受 state 作為引數,只有在這裡才可以進行狀態更改

提交 mutation 需要用到 store 的 store.commit() 方法

注意: mutation 函式必須是同步的

    // 建立 mutation
    const ADD_COUNT = 'ADD_COUNT'; // 用常量代替事件型別,使得程式碼更清晰
    const store = new Vuex.Store({
        state: {
            global: 100,
            step: 3
        },
        mutations: {
           [ADD_COUNT] (state, num) { // 第一個引數為 state 用於變更狀態
               state.global += num;
            },
           [ADD_COUNT] (state, payload) { // 第二個引數為提交的引數,引數型別視提交方式而定
              state.global += payload.num;
           },
        }
    });

    // 元件中提交
    methods: {
      handleClick() {
         this.$store.commit('ADD_COUNT', 30); // 普通提交 第二個引數為引數
         this.$store.commit('ADD_COUNT', { num: 30 }); // 可以將引數包裝到一個物件中提交
         this.$store.commit({ // 可以直接提交一個物件,物件中 type 屬性對應 事件型別,其他屬性, 成為事件處理函式的第二個引數物件中的屬性
            type: 'ADD_COUNT', 
            num: 30
         });
      },
   },

6. mapMutations 輔助函式

同 mapState 和 mapGetters 一樣,mapMutations 可以節約程式碼,使程式碼更加簡潔,他將會在元件的 methods 屬性中完成對映

   methods: {
      handleClick() {
         this.addCount({ num: 40 });
         this.ADD_COUNT({ num: 60 });
      },
      ...mapMutations({ 
         addCount: 'ADD_COUNT' // 將 this.addCount({}) 對映為 this.$store.commit('ADD_COUNT', {})
      }),
      ...mapMutations([ // 引數為陣列表示函式名不變
         'ADD_COUNT'
      ])
   },

7. action

action 類似於 mutation, 不同在於:

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

Action 函式接受一個與 store 例項具有相同方法和屬性的 context 物件,因此你可以呼叫 context.commit 提交一個 mutation,或者通過 context.state 和 context.getters 來獲取 state 和 getters。

// 建立 actions 
    const store = new Vuex.Store({
        state: {
            global: 100,
            step: 3
        },
        mutations: {
           [ADD_COUNT] (state, payload) { 
              state.global += payload.num;
           },
        },
        actions: {
            changeCount(context, payload) { // 第一個引數為 store 例項類似的物件
                context.commit({
                    type: ADD_COUNT,
                    ...payload
                });
           },
           changeCount({ commit }, payload) { // 一般使用解構賦值
              commit({
                 type: ADD_COUNT,
                 num: payload.num
              });
           },
           changeCount({ commit }) { // action 中可以盡心非同步操作,並且可以提交多次 mutation 
              request('/api').then(res => {
                 commit({
                    type: ADD_COUNT,
                    num: res.data.num
                 });
              }).catch(() => {
                 commit({
                    type: ERROR
                 });
              });
           }
        }
    });

    // Vue 元件中使用
    methods: {
      handleClick() {
         this.$store.dispatch('changeCount', { num: 1 });
         this.$store.dispatch({ // 注意如果使用這種方式,changeCount 接受的 payload 中是包含 type: 'changeCount' 欄位的 commit 的時候要去除
            type: 'changeCount',
            num: 30
        });
      },
   },

8. mapActions 輔助函式

與 mapMutations 類似,用於將 actions 對映到元件中

   methods: {
      handleClick() {
         this.changeCount({ num: 3 });
      },
      ...mapActions({
         changeCount: 'changeCount'
      })
      ...mapActions([
        'changeCount'
      ])
   },

actions 可以組合使用,也就是在一個 action 中呼叫另外一個 action

9. module

解決使用單一狀態樹,所有狀態集中,使得物件過大的問題,Vuex 允許我們將 store 分割成模組(module)。每個模組擁有自己的 state、mutation、action、getter、甚至是巢狀子模組——從上至下進行同樣方式的分割,以下是官網的例子

    const moduleA = {
      state: { ... },
      mutations: { ... },
      actions: { ... },
      getters: { ... }
    }

    const moduleB = {
      state: { ... },
      mutations: { ... },
      actions: { ... }
    }

    const store = new Vuex.Store({
      modules: {
        a: moduleA,
        b: moduleB
      }
    })

    store.state.a // -> moduleA 的狀態
    store.state.b // -> moduleB 的狀態

模組的區域性狀態

  • 對於模組內部的 mutation 和 getter,接收的第一個引數是模組的區域性狀態物件。
  • 對於模組內部的 action,區域性狀態通過 context.state 暴露出來,根節點狀態則為 context.rootState
  • 對於模組內部的 getter,根節點狀態會作為第三個引數暴露出來

10. 模組化使用方式

在輔助函式中使用 module 中資料
    export default {
        computed: {
            ...mapState({
              // 要使用 模組中的 state 需要 通過 state.[模組名].[模組資料] 引用
              moduleAData: state => state.moduleA.moduleAData
            }),
            ...mapGetters({
              //  要使用 模組中的 getter 根使用全域性的 getter 一致,因為模組內的 getter 是註冊在全域性名稱空間的
                moduleAGetterData: 'moduleAGetterData'
            })
        },
        methods: {
        //  mutation、action 與 getter 一樣,模組內的 mutation、action 也是註冊在全域性名稱空間的
            ...mapMutations({
                changeTestList: 'changeTestList'
            }),
            ...mapActions([
                changeTestList: 'changeTestList'
            ])
        },
        components: {
            SideBarMenu, BasicHeader
        }
    };
給模組新增名稱空間,以提高封裝性

模組中新增 namespaced: true 可以使其成為名稱空間模組,模組被註冊後,模組內所有 getter action mutation 都會 自動根據 模組註冊路徑調整命名 (如下)

  // 定義模組
    const moduleA = {
        namespaced: true, // 新增名稱空間
        state: {
            test: 'abc'
        },
        getters: {
            upperTest(state) {
                return state.test.toUpperCase(); 
            }
        },
        mutations: {
            changeTest(state, paylaod) {
                state.test = payload.test;
            }
        }
    }

元件中使用

    export default {
        computed: {
            ...mapState({
                test: state => state.moduleA.test // 模組中的 state 本來就是有層級的所以這裡不變
            })
            ...mapGetters({
                upperTest: 'moduleA/upperTest' // 這裡的引用要加上模組名
            })
        },
        methods: {
            ...mapMutations({
                changeTest: 'moduleA/changeTest' // action 與 mutation 與 getter 的使用一致
            })
        },
    }

元件中使用的簡易寫法 給 mapState mapGetters mapMutations mapActions 的第一個引數傳入模組的空間名稱,這樣所有繫結都會自動將該模組作為上下文。

    export default {
        computed: {
            ...mapState('moduleA', ['test']) //傳入第一個引數表示繫結的上下文
            ...mapGetters('moduleA', {
                upperTest: 'upperTest' 
            })
        },
        methods: {
            ...mapMutations('moduleA', {
                changeTest: 'changeTest' 
            })
        },
    }

11. 嚴格模式

在嚴格模式下,無論何時發生了狀態變更且不是由 mutation 函式引起的,將會丟擲錯誤。這能保證所有的狀態變更都能被除錯工具跟蹤到。

    const store = new Vuex.Store({
      strict: process.env.NODE_ENV !== 'production'
    })

對比 redux

  • Vuex 與 Redux 都使用了單一元件樹,狀態都存放在 state 中
  • 如果某個狀態的使用涉及到固定的計算處理,那麼 Vuex 中可以通過設定 getter 來提取出公共的狀態變化方法,而 Redux 中只能在各個元件中處理或者提取出一個函式
  • Vuex 中狀態更改只能通過提交(commit)mutation ,而且不能是非同步的, Redux 中 reducer 也包括這部分功能;reducer 會接收含有事件型別的物件(type屬性)然後根據 type 的型別返回新的 state ,Vuex 中的 mutation 會根據不同的事件型別,對傳入的 state 進行操作。
  • Vuex 中如果有非同步操作需要更改狀態的,要使用 action,action 用來在非同步操作後 通過 commit 提交 mutations; 在 Redux 中 action 的功能與其一致,提交更改用的是 dispatch 方法(Vuex 中也有 dispatch 方法,但是是用來觸發 action 的)。
  • Vuex 中分割模組用的是 module 屬性,這個需求在 Redux 中是通過 建立 reducer 是,分模組引入不同的 初始 state 完成的。
  • Vuex 中為了方便在元件中使用 狀態或者狀態變更函式,引入了 一些輔助函式 mapState、mapGetters、mapMutations、mapActions;Redux 為配合 React 需要引入 React-Redux 的 connect 方法,將狀態與狀態處理函式注入到元件中,實現了同樣功能

相關文章