一、前言

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

a.將資料以及操作資料的行為都定義在父元件;

b.將資料以及操作資料的行為傳遞給需要的各個子元件(有可能需要多級傳遞)

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

在這裡插入圖片描述

二、什麼是Vuex

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

三、Vuex的原理是什麼

1.簡要介紹Vuex原理

在這裡插入圖片描述

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來實現如下效果?

在這裡插入圖片描述

這個小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 原始碼分析

來源:https://segmentfault.com/a/1190000016768961