Vuex 狀態管理模式

  • state: 最底層的初始數據
  • getters: 相當于vue的計算屬性,對state數據進行處理 、擴展
  • mutations: 當需要修改state時,在這里定義mutations
  • actions: 當需要對多個mutations進行處理時,在actions進行mutations派發,異步處理也是在這里定義

這是使用方法、后面為介紹:

// 簡單使用 定義目錄 src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import createLogger from 'vuex/dist/logger'

Vue.use(Vuex)

// 開啟變化顯示
const debug = process.env.NODE_ENV !== 'production'

const state = {
  name: 'zfx'
}

const getters = {}

const mutations = {
  set_name(state, data) {
      state.name = data
  }
}

const actions = {}
export default new Vuex.Store({
    state,
    getters,
    mutations,
    actions,
    strict: debug,
    plugins: debug ? [createLogger()] : []
})

// 使用目錄 src/pages/index.vue
 import { mapState, mutations } from 'vuex'
...
// state、getters 在computed計算屬性引入
// mutations、 actions在mounted引入 格式如下:
mounted() { 
  ...mutations([
    'set_name'
  ])
}
...

Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。

什么是“狀態管理模式”?

讓我們從一個簡單的 Vue 計數應用開始:

new Vue({
  // state 初始狀態(數據)
  data () {
    return {
      count: 0
    }
  },
  // view 視圖
  template: `
    <div>{{ count }}</div>
  `,
  // actions 變化(模型)
  methods: {
    increment () {
      this.count++
    }
  }
})

這個狀態自管理應用包含以下幾個部分:

  • state,驅動應用的數據源;
  • view,以聲明方式將 state 映射到視圖;
  • actions,響應在 view 上的用戶輸入導致的狀態變化。

以下是一個表示“單向數據流”理念的極簡示意:
[圖片上傳失敗...(image-46465a-1514178818777)]
但是,當我們的應用遇到多個組件共享狀態時,單向數據流的簡潔性很容易被破壞:

  • 多個視圖依賴于同一狀態。
  • 來自不同視圖的行為需要變更同一狀態。

對于問題一,傳參的方法對于多層嵌套的組件將會非常繁瑣,并且對于兄弟組件間的狀態傳遞無能為力。對于問題二,我們經常會采用父子組件直接引用或者通過事件來變更和同步狀態的多份拷貝。以上的這些模式非常脆弱,通常會導致無法維護的代碼。

因此,我們為什么不把組件的共享狀態抽取出來,以一個全局單例模式管理呢?在這種模式下,我們的組件樹構成了一個巨大的“視圖”,不管在樹的哪個位置,任何組件都能獲取狀態或者觸發行為!

另外,通過定義和隔離狀態管理中的各種概念并強制遵守一定的規則,我們的代碼將會變得更結構化且易維護。

這就是 Vuex 背后的基本思想,借鑒了

FluxRedux、和

The Elm Architecture。與其他模式不同的是,Vuex 是專門為 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。

開始

每一個 Vuex 應用的核心就是 store(倉庫),“store”基本上就是一個容器,它包含著你的應用中大部分的狀態 (state)。Vuex 和單純的全局對象有以下兩點不同:

  1. Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新。

  2. 你不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地提交 mutation(變化)。這樣使得我們可以方便地跟蹤每一個狀態的變化,從而讓我們能夠實現一些工具幫助我們更好地了解我們的應用。

最簡單的 Store

僅需要提供一個初始 state 對象和一些 mutation:

// 如果在模塊化構建系統中,請確保在開頭調用了

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {  //狀態
    count: 0
  },
  mutations: {  //變化
    increment (state) {
      state.count++
    }
  }
})

現在,你可以通過 store.state 來獲取狀態對象,以及通過 store.commit 方法觸發狀態變更:

console.log(store.state.count) // -> 1

store.commit('increment')

再次強調,我們通過提交 mutation 的方式,而非直接改變 store.state.count,是因為我們想要更明確地追蹤到狀態的變化。這個簡單的約定能夠讓你的意圖更加明顯,這樣你在閱讀代碼的時候能更容易地解讀應用內部的狀態改變。此外,這樣也讓我們有機會去實現一些能記錄每次狀態改變,保存狀態快照的調試工具。有了它,我們甚至可以實現如時間穿梭般的調試體驗。

由于 store 中的狀態是響應式的,在組件中調用 store 中的狀態簡單到僅需要在計算屬性中返回即可。觸發變化也僅僅是在組件的 methods 中提交 mutation。

核心概念

State

Vuex 使用單一狀態樹——是的,用一個對象就包含了全部的應用層級狀態。至此它便作為一個“唯一數據源 (SSOT)”而存在。這也意味著,每個應用將僅僅包含一個 store 實例。單一狀態樹讓我們能夠直接地定位任一特定的狀態片段,在調試的過程中也能輕易地取得整個當前應用狀態的快照。

Vuex 通過 store 選項,提供了一種機制將狀態從根組件“注入”到每一個子組件中(需調用 Vue.use(Vuex)):

Vue.use(Vuex)

const app = new Vue({
  el: '#app',
  // 把 store 對象提供給 “store” 選項,這可以把 store 的實例注入所有的子組件
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})

通過在根實例中注冊 store 選項,該 store 實例會注入到根組件下的所有子組件中,且子組件能通過 this.$store 訪問到。讓我們更新下 Counter 的實現:

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

mapState 輔助函數

當一個組件需要獲取多個狀態時候,將這些狀態都聲明為計算屬性會有些重復和冗余。為了解決這個問題,我們可以使用 mapState 輔助函數幫助我們生成計算屬性,讓你少按幾次鍵:

// 在單獨構建的版本中輔助函數為 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // 箭頭函數可使代碼更簡練
    count: state => state.count,

    // 傳字符串參數 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 為了能夠使用 `this` 獲取局部狀態,必須使用常規函數
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

當映射的計算屬性的名稱與 state 的子節點名稱相同時,我們也可以給 mapState 傳一個字符串數組。

computed: mapState([
  // 映射 this.count 為 store.state.count
  'count'
])

對象展開運算符

mapState 函數返回的是一個對象。我們如何將它與局部計算屬性混合使用呢?通常,我們需要使用一個工具函數將多個對象合并為一個,以使我們可以將最終對象傳給computed 屬性。但是自從有了對象展開運算符(現處于 ECMASCript 提案 stage-3 階段),我們可以極大地簡化寫法:

computed: {
  // 使用對象展開運算符將此對象混入到外部對象中
  ...mapState({
     'count'
  })
}

組件仍然保有局部狀態

使用 Vuex 并不意味著你需要將所有的狀態放入 Vuex。雖然將所有的狀態放到 Vuex 會使狀態變化更顯式和易調試,但也會使代碼變得冗長和不直觀。如果有些狀態嚴格屬于單個組件,最好還是作為組件的局部狀態。你應該根據你的應用開發需要進行權衡和確定。

Getter

有時候我們需要從 store 中的 state 中派生出一些狀態,例如對列表進行過濾并計數:

computed: {
  doneTodosCount () {
    return this.$store.state.todos.filter(todo => todo.done).length
  }
}

如果有多個組件需要用到此屬性,我們要么復制這個函數,或者抽取到一個共享函數然后在多處導入它——無論哪種方式都不是很理想。

Vuex 允許我們在 store 中定義“getter”(可以認為是 store 的計算屬性)。就像計算屬性一樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變才會被重新計算。

Getter 接受 state 作為其第一個參數:

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

Getter 會暴露為 store.getters 對象:

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

Getter 也可以接受其他 getter 作為第二個參數:

getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}
store.getters.doneTodosCount // -> 1

你也可以通過讓 getter 返回一個函數,來實現給 getter 傳參。在你對 store 里的數組進行查詢時非常有用。

getters: {
  // ...
  getTodoById: (state) => (id) => {
    // 過濾器 返回id === 2的數據
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

mapGetters 輔助函數

mapGetters 輔助函數僅僅是將 store 中的 getter 映射到局部計算屬性:

import { mapGetters } from 'vuex'

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

如果你想將一個 getter 屬性另取一個名字,使用對象形式:

mapGetters({
 // 映射 `this.doneCount` 為 `store.getters.doneTodosCount`
 doneCount: 'doneTodosCount'
})

Mutation同步事務

更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。

提交載荷(Payload)

你可以向 store.commit 傳入額外的參數,即 mutation 的 載荷

mutations: {
  increment (state, n) {
    state.count += n
  }
}
store.commit('increment', 10)

在大多數情況下,載荷應該是一個對象,這樣可以包含多個字段并且記錄的 mutation 會更易讀:

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
store.commit('increment', {
  amount: 10
})

可以這樣創建

//創建
export default new Vuex.Store({
    mutations
})
//接收
 ...mapMutations([
            'set_audio_data',   // 設置audio數據
            'set_playMode',     // 設置播放模式
            'set_playList'      // 設置播放列表數據
        ]),
如果這樣創建Vuex, 可以用
this.set_playMode(val) 調用

Mutation 需遵守 Vue 的響應規則

既然 Vuex 的 store 中的狀態是響應式的,那么當我們變更狀態時,監視狀態的 Vue 組件也會自動更新。這也意味著 Vuex 中的 mutation 也需要與使用 Vue 一樣遵守一些注意事項:

  1. 最好提前在你的 store 中初始化好所有所需屬性。

  2. 當需要在對象上添加新屬性時,你應該

  • 使用Vue.set(obj, 'newProp', 123), 或者

  • 以新對象替換老對象。例如,利用 stage-3 的對象展開運算符我們可以這樣寫:

    如:let x1 = { a: 1, b: 2 };
            x1 = { ...x1, c: 3 };
       現在 x1 為 { a: 1, b: 2, c: 3 };
    
    state.obj = { ...state.obj, newProp: 123 }
    

Mutation 必須是同步函數

不能使用回調函數

在組件中提交 Mutation

你可以在組件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 輔助函數將組件中的 methods 映射為 store.commit 調用(需要在根節點注入 store)。

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 將 `this.increment()` 映射為 `this.$store.commit('increment')`

      // `mapMutations` 也支持載荷:
      'incrementBy' // 將 `this.incrementBy(amount)` 映射為 `this.$store.commit('incrementBy', amount)`
    ]),

  this.incrementBy(); 調用

    ...mapMutations({
      add: 'increment' // 將 `this.add()` 映射為 `this.$store.commit('increment')`
    })
  }

}

Action異步操作

Action 類似于 mutation,不同在于:

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

讓我們來注冊一個簡單的 action:用到了 ES2015 的參數解構 來簡化代碼

actions: {
  increment ({ commit }) {
    commit('increment')  //commit提交mutation
  }
}

分發 Action

Action 通過 store.dispatch 方法觸發:

store.dispatch('increment')

Actions 支持同樣的載荷方式和對象方式進行分發:

// 以載荷形式分發
store.dispatch('incrementAsync', {
  amount: 10
})

// 以對象形式分發
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

async和await用法

  1. 只有在async方法里面才能使用await操作符;
  2. await操作符是針對Task對象的;
  3. 當方法A調用方法B,方法B方法體內又通過await調用方法C時,如果方法C內部有異步操作,則方法B會等待異步操作執行完,才往下執行;但方法A可以繼續往下執行,不用再等待B方法執行完。

在組件中分發 Action

使用 mapActions 輔助函數將組件的 methods 映射為 store.dispatch 調用(需要先在根節點注入 store):

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 將 `this.increment()` 映射為 `this.$store.dispatch('increment')`

      // `mapActions` 也支持載荷:
      'incrementBy' // 將 `this.incrementBy(amount)` 映射為 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 將 `this.add()` 映射為 `this.$store.dispatch('increment')`
    })
  }
}

組合 Action

Action 通常是異步的,那么如何知道 action 什么時候結束呢?更重要的是,我們如何才能組合多個 action,以處理更加復雜的異步流程?

首先,你需要明白 store.dispatch 可以處理被觸發的 action 的處理函數返回的 Promise,并且 store.dispatch 仍舊返回 Promise:

// 假設 getData() 和 getOtherData() 返回的是 Promise

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成,再繼續執行
    commit('gotOtherData', await getOtherData())
  }
}

一個 store.dispatch 在不同模塊中可以觸發多個 action 函數。在這種情況下,只有當所有觸發函數完成后,返回的 Promise 才會執行。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容

  • 對于學習過react的同學可能比較清楚,在react我們是通過redux來處理狀態管理的,那么現在火熱的vue是如...
    我是上帝可愛多閱讀 2,871評論 0 4
  • Vuex是什么? Vuex 是一個專為 Vue.js應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件...
    蕭玄辭閱讀 3,133評論 0 6
  • 安裝 npm npm install vuex --save 在一個模塊化的打包系統中,您必須顯式地通過Vue.u...
    蕭玄辭閱讀 2,962評論 0 7
  • Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應...
    白水螺絲閱讀 4,678評論 7 61
  • 系列文章:Vue 2.0 升(cai)級(keng)之旅Vuex — The core of Vue applic...
    6ed7563919d4閱讀 4,562評論 2 58