vue 學習筆記 - vuex

MADAO是不會開花的發表於2019-02-27

很早就聽說過vuex,但是一直沒用過。正好做到了登入這一塊,趁著這個機會掌握它。

一. 對Vuex的理解

Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式。 ---引用自Vuex官網

我個人的理解是這樣的,就拿登入來說,登入是一種狀態,在網站中有可能登入後的好多頁面和未登入的狀態都是不同的,把每個頁面想象成元件,也就是說多個元件都要共享這個登入狀態,如果是採用傳參的方式來共享,會變得很繁瑣,就像是多個函式巢狀一樣,要一層一層的將引數傳遞下去,而且不易維護,這時候就需要將這種多個元件共享的狀態或是資料抽取出來,單獨存放在一個地方,只要需要這些狀態或者資料的元件,都去這個地方找就好了,那麼實現這個功能的東西就叫做Vuex。

二. Vuex中的核心概念

  • state

    state就是資料來源,也就是說所有共享的資料都存放在這個地方,例子:

    使用vue-cli搭建的專案,會有一個store.js的檔案

    vue 學習筆記 - vuex

    import Vue from "vue";
    import Vuex from "vuex";
    
    Vue.use(Vuex);
    
    export default new Vuex.Store({
        state: {
            isLogin: false  // state裡面就可以放共享的資料
        },
        mutations: {},
        actions: {}
    });
    
    複製程式碼

    其他元件如果要獲取state裡面的資料,可以通過this.$store.state.xxx獲得

    export default {
        name: "HelloWorld",
        props: {
            msg: String
        },
        mounted() {
            console.log(this.$store.state.isLogin)
        },
    };
    複製程式碼
    • mapState

      假如一個元件需要獲取多個共享資料,例子:

      // store.js
      
      export default new Vuex.Store({
          state: {
              isLogin: false,
              name: "admin",
              password: 123456
          },
          mutations: {},
          actions: {}
      });
      複製程式碼
      // HelloWorld.vue
      
      export default {
          name: "HelloWorld",
          props: {
              msg: String
          },
          computed: {
              isLogin() {
                  return this.$store.state.isLogin
              },
              name() {
                  return this.$store.state.name
              },
              passsword() {
                  return this.$store.state.password
              }
          },
      };
      複製程式碼

      例子中是一個一個的寫出來,3個還好。假如有10個寫起來就很麻煩了。

      mapState可以解決這個問題:

      上面的例子用mapState寫就是這樣的:

      // HelloWorld.vue
      import { mapState } from 'vuex';
      export default {
          name: "HelloWorld",
          props: {
              msg: String
          },
          computed: mapState(['isLogin', 'name', 'password'])
      };
      複製程式碼

      mapState的引數如果是陣列的話,那陣列的每一項就是state中資料的key值,按照官網的說法是:對映 this.isLogin 為 store.state.isLogin。

      但是computed不可能只有state裡的屬性,還有元件自身的屬性,這時候就要使用物件展開運算子了:

      // HelloWorld.vue
      import { mapState } from 'vuex';
      export default {
          name: "HelloWorld",
          props: {
              msg: String
          },
          computed: {
              greetings() {
                  return `${this.name}!你好`
              },
              ...mapState(['isLogin', 'name', 'password'])
          }
      };
      複製程式碼

      結果:

      vue 學習筆記 - vuex

      mapState還可以傳入一個物件,比如上面例子可以改寫成這樣

      // HelloWorld.vue
      import { mapState } from 'vuex';
      export default {
          name: "HelloWorld",
          props: {
              msg: String
          },
          computed: {
              ...mapState({
                  isLogin: state => state.isLogin,
                  name: state => state.isLogin,
                  password: 'password',
                  greetings(state) {
                      return `${state.name}!你好`
                  }
              })
          }
      };
      複製程式碼

      結果:

      vue 學習筆記 - vuex

      這兩種寫法都可獲得state中對應key值得資料

      isLogin: state => state.isLogin
      password: 'password'
      複製程式碼

      依賴state中的計算屬性可以使用例子中函式的方式,

      greetings(state) {
          return `${state.name}!你好`
      }
      複製程式碼

      注意不要使用箭頭函式,為了保證this指向正確(沒看過原始碼,不知道呼叫方式,所以不能確定this的指向,但是官方文件說不要使用箭頭函式,按照文件的來)

    • Getter

      官方文件上有一句話我覺得已經解釋的很清楚了:

      可以認為是 store 的計算屬性 ---引用自Vuex官網

      直接看例子:

      // store.js
      export default new Vuex.Store({
          state: {
              todeList: [
                  { task: "追番", isDone: false },
                  { task: "玩遊戲", isDone: false },
                  { task: "睡覺", isDone: true }
              ]
          },
          mutations: {},
          actions: {}
      });
      複製程式碼

      現在需要獲得todeList中已完成的事項數量。如果只有一個元件需要,那麼直接在元件內部filter就好了,如果是多個元件都要,那又出現了重複程式碼的情況,這種情況下可以使用getters解決:

      // store.js
      export default new Vuex.Store({
          state: {
              todeList: [
                  { task: "追番", isDone: false },
                  { task: "玩遊戲", isDone: false },
                  { task: "睡覺", isDone: true }
              ]
          },
          getters: {
              doneList: state => {
                  return state.todeList.filter(task => task.isDone);
              }
          }
          mutations: {},
          actions: {}
      });
      複製程式碼
      // HelloWorld.vue
      
      export default {
          name: "HelloWorld",
          props: {
              msg: String
          },
          computed: {
              doneAmount() {
                  return this.$store.getters.doneList.length;
              }
          }
      };
      複製程式碼

      每當state中的資料變化,getter中依賴它的資料就會重新計算,在各個元件中也就不用重複的去寫那些方法了,直接獲取即可。

      getters中的getter還有幾種其他的寫法:

      • getter可以引用其他getter。上面的例子可以改成這樣:

        // store.js
        export default new Vuex.Store({
            state: {
                todeList: [
                    { task: "追番", isDone: false },
                    { task: "玩遊戲", isDone: false },
                    { task: "睡覺", isDone: true }
                ]
            },
            getters: {
                doneList: state => {
                    return state.todeList.filter(task => task.isDone);
                },
                doneAmount: (state, getters) => {
                    return getters.doneList.length;
                }
            },
            mutations: {},
            actions: {}
        });
        複製程式碼
        // HelloWorld.vue
        export default {
            name: "HelloWorld",
            props: {
                msg: String
            },
            computed: {
                doneAmount() {
                    return this.$store.getters.doneAmount;
                }
            }
        };
        複製程式碼

        這樣改完之後元件裡直接用doneAmount就行了,getters中的doneAmount會根據doneList的改變而重新計算出數量。

      • getter可以返回一個函式,使用的時候就可以給getter傳引數了

        // store.js
        export default new Vuex.Store({
            state: {
                todeList: [
                    { task: "追番", isDone: false },
                    { task: "玩遊戲", isDone: false },
                    { task: "睡覺", isDone: true }
                ]
            },
            getters: {
                getTask: state => taskName => {
                    return state.todeList.find(taskItem => taskName === taskItem.task);
                }
            },
            mutations: {},
            actions: {}
        });
        複製程式碼
        // HelloWorld.vue
        export default {
            name: "HelloWorld",
            props: {
                msg: String
            },
            computed: {
                task() {
                    return this.$store.getters.getTask('玩遊戲')
                }
            }
        };
        複製程式碼

        vue 學習筆記 - vuex

      同樣,getter也有輔助的函式mapGetters,它和mapState差不多,這裡就不繼續說了。

    • Mutation

      想要改變state中的資料,在vuex中唯一的辦法就是提交一個mutation,直接看看它的使用方式吧,例子:

      • 不帶引數

        // store.js
        export default new Vuex.Store({
            state: {
                num: 0
            },
            getters: {},
            mutations: {
                addOne(state) {
                    state.num++
                }
            },
            actions: {}
        });
        複製程式碼
        // HelloWorld.vue
        export default {
            name: "HelloWorld",
            props: {
                msg: String
            },
            mounted() {
                this.$store.commit('addOne')
                console.log(this.num)
            },
            computed: {
                num() {
                    return this.$store.state.num
                }
            }
        };
        複製程式碼
      • 帶引數

        // store.js
        export default new Vuex.Store({
            state: {
                num: 0
            },
            getters: {},
            mutations: {
                add(state, n) {
                    state.num += n
                },
                multiplication(state, payload) {
                    state.num *= payload.num;
                }
            },
            actions: {}
        });
        複製程式碼
        // HelloWorld.vue
        export default {
            name: "HelloWorld",
            props: {
                msg: String
            },
            mounted() {
                this.$store.commit('add', 10)
                this.$store.commit('multiplication', {
                    num: 10
                })
                this.$store.commit({
                    type: 'multiplication',
                    num: 10
                })
                console.log(this.num)
            },
            computed: {
                num() {
                    return this.$store.state.num
                }
            }
        };
        複製程式碼

        帶引數方式中如果引數是物件,有兩種寫法,一種是把mutation的名字作為第一個引數,第二種是第一個引數是物件,物件中有個type屬性,type屬性的值就是mutation的名字。

      mutation也有輔助函式mapMutations,上面例子可以寫成下面這樣:

      // HelloWorld.vue
      import { mapMutations } from "vuex";
      export default {
          name: "HelloWorld",
          props: {
              msg: String
          },
          mounted() {
              this.add(10)
              this.multiplication({
                  num: 100
              })
          },
          methods: {
              ...mapMutations([
                  'add',
                  'multiplication'
              ])
          },
          computed: {
              num() {
                  return this.$store.state.num
              }
          }
      };
      複製程式碼

      mutation還需要注意的兩點有:

      • mutation事件名建議使用常量代替(官方推薦):

        // 常量也建議全部存放在單獨的檔案中
        const MUTATION_ADD = 'MUTATION_ADD'
        
        
        mutations: {
            [MUTATION_ADD](state, n) {
                state.num += n
            }
        }
        複製程式碼
      • mutation 必須是同步函式,原因參考官網

    • Action

      由於mutation只能是同步函式,如果有非同步的操作,那麼就可以用action。例子:

      export default new Vuex.Store({
          state: {
              num: 1
          },
          getters: {},
          mutations: {
              add(state, payload) {
                  state.num += payload.num;
              }
          },
          actions: {
              add(context, payload) {
                  setTimeout(() => {
                      context.commit("add", payload);
                  }, 1000);
              }
          }
      });
      複製程式碼

      action就好像吧mutation包了一層一樣,在action中就可以使用各種非同步了。 元件中使用action用的是這樣的方式:

      this.$store.dispatch('xxx')
      this.$store.dispatch('xxx', {key: value})
      this.$store.dispatch({
          type: 'xxx',
          key: value
      })
      複製程式碼

      action還可以這樣用:

      actions: {
          actionOne() {
              return new Promise(reslove => {
                  console.log("呆死ki");
                  reslove();
              });
          },
          add(context, payload) {
              context.dispatch("actionOne").then(() => {
                  setTimeout(() => {
                      context.commit("add", payload);
                  }, 1000);
              });
          }
      }
      複製程式碼

      它的輔助函式叫mapActions,用法和mapMutations類似。

    最後剩Module,目前還沒用過,所以理解可能會有問題。這裡就不說了。

    總結下就是:

    vuex中:

    • store:作為一個容器,裡面包含了state,mutation等等,就像一個vue的例項一樣。
    • state:store中的資料來源。
    • getter:store中的計算屬性
    • mutation:修改state的唯一方法就是提交一個mutation,mutation只能是同步函式
    • action:由於mutation只能是同步函式,那麼有非同步的操作就要用到action,action就好像將mutation包裹了一層一樣。
    • module: 如果store特別的龐大,可以使用module將它分割成模組。

相關文章