從頭開始學習Vuex

一、前言

當我們的應用遇到多個組件共享狀態時,會需要多個組件依賴於同一狀態抑或是來自不同視圖的行爲需要變更同一狀態。以前的解決辦法:

a.將數據以及操作數據的行爲都定義在父組件;

b.將數據以及操作數據的行爲傳遞給需要的各個子組件(有可能需要多級傳遞)

傳參的方法對於多層嵌套的組件將會非常繁瑣,並且對於兄弟組件間的狀態傳遞無能爲力。在搭建下面頁面時,你可能會對 vue 組件之間的通信感到崩潰 ,特別是非父子組件之間通信。此時就應該使用vuex,輕鬆可以搞定組件間通信問題。

組件間通信

二、什麼是Vuex

Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。這裏的關鍵在於集中式存儲管理。簡單來說,對 vue 應用中多個組件的共享狀態進行集中式的管理(讀/寫)

三、Vuex的原理是什麼

1.簡要介紹Vuex原理

image

Vuex實現了一個單向數據流,在全局擁有一個State存放數據,當組件要更改State中的數據時,必須通過Mutation進行,Mutation同時提供了訂閱者模式供外部插件調用獲取State數據的更新。而當所有異步操作(常見於調用後端接口異步獲取更新數據)或批量的同步操作需要走Action,但Action也是無法直接修改State的,還是需要通過Mutation來修改State的數據。最後,根據State的變化,渲染到視圖上。

2.簡要介紹各模塊在流程中的主要功能:

  • Vue Components:Vue組件。HTML頁面上,負責接收用戶操作等交互行爲,執行dispatch方法觸發對應action進行迴應。
  • dispatch:操作行爲觸發方法,是唯一能執行action的方法。
  • actions:操作行爲處理模塊,由組件中的$store.dispatch('action 名稱', data1)來觸發。然後由commit()來觸發mutation的調用 , 間接更新 state。負責處理Vue Components接收到的所有交互行爲。包含同步/異步操作,支持多個同名方法,按照註冊的順序依次觸發。向後臺API請求的操作就在這個模塊中進行,包括觸發其他action以及提交mutation的操作。該模塊提供了Promise的封裝,以支持action的鏈式觸發。
  • commit:狀態改變提交操作方法。對mutation進行提交,是唯一能執行mutation的方法。
  • mutations:狀態改變操作方法,由actions中的commit('mutation 名稱')來觸發。是Vuex修改state的唯一推薦方法。該方法只能進行同步操作,且方法名只能全局唯一。操作之中會有一些hook暴露出來,以進行state的監控等。
  • state:頁面狀態管理容器對象。集中存儲Vue components中data對象的零散數據,全局唯一,以進行統一的狀態管理。頁面顯示所需的數據從該對象中進行讀取,利用Vue的細粒度數據響應機制來進行高效的狀態更新。
  • getters:state對象讀取方法。圖中沒有單獨列出該模塊,應該被包含在了render中,Vue Components通過該方法讀取全局state對象。

四、什麼時候使用Vuex

雖然 Vuex 可以幫助我們管理共享狀態,但也附帶了更多的概念和框架。這需要對短期和長期效益進行權衡。
如果您的應用夠簡單,您最好不要使用 Vuex,因爲使用 Vuex 可能是繁瑣冗餘的。一個簡單的 global event bus 就足夠您所需了。但是,如果您需要構建一箇中大型單頁應用,您很可能會考慮如何更好地在組件外部管理狀態,Vuex 將會成爲自然而然的選擇。

五、Vuex安裝(限定開發環境爲 vue-cli)

首先要安裝vue-cli腳手架,對於大陸用戶,建議將npm的註冊表源設置爲國內的鏡像(淘寶鏡像),可以大幅提升安裝速度。

npm config set registry https://[registry.npm.taobao.org](http://registry.npm.taobao.org/)
npm config get registry//配置後可通過下面方式來驗證是否成功
npm install -g cnpm --registry=[https://registry](https://registry/).npm.taobao.org
//cnpm安裝腳手架
cnpm install -g vue-cli
vue init webpack my-vue
cd my-vue
cnpm install
cnpm run dev

腳手架安裝好後,再安裝vuex

cnpm install vuex --save

六、如何使用Vuex

### 1.如何通過Vue來實現如下效果?
image

這個小demo很容易用vue實現,核心代碼如下:

  <div class="hello">
    <p>click {{count}} times,count is {{evenOrOdd}}</p>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="incrementIfOdd">increment if odd</button>
    <button @click="incrementAsync">increment async</button>
  </div>
  ......
  export default {
  name: "HelloWorld",
  data() {
    return {
      count: 0
    };
  },
  computed: {
    evenOrOdd() {
      return this.count % 2 === 0 ? "偶數" : "奇數";
    }
  },
  methods: {
    increment() {
      this.count = this.count + 1;
    },
    decrement() {
      this.count = this.count - 1;
    },
    // 只有是奇數才加1
    incrementIfOdd() {
      if (this.count % 2 === 1) {
        this.count = this.count + 1;
      }
    },
    // 過兩秒才加1
    incrementAsync() {
      setInterval(() => {
        this.count = this.count + 1;
      }, 2000);
    }
  }
}

2.如何通過Vuex來改造上面代碼?

①創建一個store.js文件

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {// 包含了多個直接更新state函數的對象
        INCREMENT(state) {
            state.count = state.count + 1;
        },
        DECREMENT(state) {
            state.count = state.count - 1;
        }
    },
    getters: {   // 當讀取屬性值時自動調用並返回屬性值
        evenOrOdd(state) {
            return state.count % 2 === 0 ? "偶數" : "奇數";
        }
    },
    actions: { // 包含了多個對應事件回調函數的對象
        incrementIfOdd({ commit, state }) { // 帶條件的action
            if (state.count % 2 === 1) {
                commit('INCREMENT')
            }
        },
        incrementAsync({ commit }) { //異步的action
            setInterval(() => {
                commit('INCREMENT')
            }, 2000);
        }

    }
})
export default store //用export default 封裝代碼,讓外部可以引用

②在main.js文件中引入store.js文件

import store from './store'
new Vue({
  el: '#app',
  router,
  store,//註冊上vuex的store: 所有組件對象都多一個屬性$store
  components: { App },
  template: '<App/>'
})

③新建一個模板HelloWorld.vue

<template>
  <div class="hello">
    <p>click {{count}} times,count is {{evenOrOdd}}</p>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="incrementIfOdd">increment if odd</button>
    <button @click="incrementAsync">increment async</button>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  computed: {
    count() {
      return this.$store.state.count;
    },
    evenOrOdd() {
      return this.$store.getters.evenOrOdd;
    }
  },
  methods: {
    increment() {
      this.$store.commit("INCREMENT");
    },
    decrement() {
      this.$store.commit("DECREMENT");
    },
    // 只有是奇數才加1
    incrementIfOdd() {
      this.$store.dispatch("incrementIfOdd"); //觸發store中對應的action調用
    },
    // 過兩秒才加1
    incrementAsync() {
      this.$store.dispatch("incrementAsync");
    }
  }
};
</script>

由於 store 中的狀態是響應式的,當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那麼相應的組件也會相應地得到高效更新。在組件中調用 store 中的狀態簡單到僅需要在計算屬性中返回即可。改變store 中的狀態的唯一途徑就是顯式地提交 (commit) mutations。

3.如何通mapState等輔助函數優化上面代碼?

import { mapActions, mapGetters, mapState, mapMutations } from "vuex";
...
 computed: {
    ...mapState(["count"]),
    ...mapGetters(["evenOrOdd"])
    }
  methods: {
    ...mapActions(["incrementIfOdd", "incrementAsync"]),
    ...mapMutations(["increment", "decrement"])
    }

有點必須要注意:HelloWorld.vue文件中increment函數名稱要跟store.js文件mutations中一致,纔可以寫成 ...mapMutations(["increment", "decrement"]),同樣的道理,incrementIfOdd和incrementAsync也要和store.js文件actions保持一致。

七、使用Vuex的注意點

1.如何在Mutations裏傳遞參數

先store.js文件裏給add方法加上一個參數n

  mutations: {
    INCREMENT(state,n) {
      state.count+=n;
    },
    DECREMENT(state){
        state.count--;
    }
  }

然後在HelloWorld.vue裏修改按鈕的commit( )方法傳遞的參數

 increment() {
      return this.$store.commit("INCREMENT",2);
    },
 decrement() {
      return this.$store.commit("DECREMENT");
    }

2.如何理解getters

getters從表面是獲得的意思,可以把他看作在獲取數據之前進行的一種再編輯,相當於對數據的一個過濾和加工。getters就像計算屬性一樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被重新計算。

例如:要對store.js文件中的count進行操作,在它輸出前,給它加上100。

首先要在store.js裏Vuex.Store()裏引入getters

getters:{
     count:state=>state.count+=100
  }

然後在HelloWorld.vue中對computed進行配置,在vue 的構造器裏邊只能有一個computed屬性,如果你寫多個,只有最後一個computed屬性可用,所以要用展開運算符”…”對上節寫的computed屬性進行一個改造。

 computed: {
   ...mapGetters(["count"])
}

3.actions和mutations區別

actions和上面的Mutations功能基本一樣,不同點是,actions是異步的改變state狀態,而Mutations是同步改變狀態

同步的意義在於這樣每一個 mutation 執行完成後都可以對應到一個新的狀態(和 reducer 一樣),這樣 devtools 就可以打個 snapshot 存下來,然後就可以隨便 time-travel 了。如果你開着 devtool 調用一個異步的 action,你可以清楚地看到它所調用的 mutation 是何時被記錄下來的,並且可以立刻查看它們對應的狀態----尤雨溪

ps:如果想訪問源代碼,請猛戳git地址

如果覺得文章對你有些許幫助,歡迎在我的GitHub博客點贊和關注,感激不盡!

參考文章

vuex官方文檔

Vuex 2.0 源碼分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章