vue狀態管理機制探究(eventBus vs VUEX)

最初瞭解到eventBus和vuex並不清楚其原理只知道無腦用,甚至有些時候用的時候還不是很清楚,最近比較好奇這二者的區別,特以此文梳理vue

eventBus

定義

  • 又稱事件總線,做爲組件共同的事件中心
  • 組件均可以上下平行的通知其餘組件

工做原理

  • 發佈訂閱模式
  • 建立一個vue實例,經過一個空的vue實例做爲橋樑實現vue組件間的通訊

缺點

  • 不太方便,若不慎使用會形成難以維護的災難,因此才須要更完善的vuex做爲狀態管理中心,將通知的概念上升到共享狀態層次

使用

  • 初始化
    • 方法一
    // event-bus.js
    import Vue from 'vue'
    export const EventBus = new Vue()
    複製代碼
    • 方法二

    建立一個全局事件總線vuex

    // main.js
    Vue.prototype.$EventBus = new Vue()
    複製代碼
  • 能夠維護一份公用數據或方法
export default new Vue({
    data() {
        return {
            searchShow: true,
            showBtn: true
        }
    },
    methods: {
        searchShowChange(value) {
            if (typeof value === 'boolean') {
                this.searchShow = false
            } else {
                this.searchShow = !this.searchShow
            }
            this.$emit('searchShowChange')
        }
    }
})
複製代碼
  • $on/$emit/$off
    • $emit發送事件
    • $on接收事件
    • $off移除監聽者
      • 十分重要,若是不手動清除,會一直存在,反覆進入到接受組件內操做獲取數據本來只有一次的操做將會有屢次
      • 移除單個事件EventBus.$off('aMsg')
      • 移除全部事件EventBus.$off()
    beforeDestroy() {
        //組件銷燬前須要解綁事件。不然會出現重複觸發事件的問題
        this.bus.$off(this.$route.path);
     }
    複製代碼

VUEX

產生背景

當咱們的應用遇到多個組件共享狀態時,單向數據流的簡潔性很容易被破壞: 1.多個視圖依賴於同一狀態。 2.來自不一樣視圖的行爲須要變動同一狀態。設計模式

對於問題一,傳參的方法對於多層嵌套的組件將會很是繁瑣,而且對於兄弟組件間的狀態傳遞無能爲力。對於問題二,咱們常常會採用父子組件直接引用或者經過事件來變動和同步狀態的多份拷貝。以上的這些模式很是脆弱,一般會致使沒法維護的代碼。數組

工做原理

  • 運用到了js設計模式中的單例模式,單例模式想要作到的是,無論咱們嘗試去建立多少次,它都只給你返回第一次所建立的那惟一的一個實例。
  • 在這種模式下,咱們的組件樹構成了一個巨大的「視圖」,無論在樹的哪一個位置,任何組件都能獲取狀態或者觸發行爲!
  • 經過定義和隔離狀態管理中的各類概念並經過強制規則維持視圖和狀態間的獨立性,咱們的代碼將會變得更結構化且易維護。

定義

是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。緩存

使用

state
  • 在計算屬性中返回某個狀態

缺點:須要頻繁地導入,而且在測試組件時須要模擬狀態bash

computed: {
    count () {
      return store.state.count
    }
}
複製代碼
  • 經過 store 選項從根組件「注入」到每個子組件中(需調用 Vue.use(Vuex))

子組件能經過 this.$store 訪問到app

const app = new Vue({
  el: '#app',
  // 把 store 對象提供給 「store」 選項,這能夠把 store 的實例注入全部的子組件
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}
複製代碼
  • 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
    }
  })
}
複製代碼
  • 給 mapState 傳一個字符串數組
computed: mapState([
  // 映射 this.count 爲 store.state.count
  'count'
])
複製代碼
  • 對象展開運算符與局部計算屬性混合使用
computed: {
  localComputed () { /* ... */ },
  // 使用對象展開運算符將此對象混入到外部對象中
  ...mapState({
    // ...
  })
}
複製代碼
Getter
  • 使用情景
    • 從 store 中的 state 中派生出一些狀態,例如對列表進行過濾並計數
  • 以屬性的形式訪問
store.getters.doneTodos
複製代碼
  • 接收兩個參數:state/getters
getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}
複製代碼
  • 經過方法訪問

注意,getter 在經過方法訪問時,每次都會去進行調用,而不會緩存結果。異步

getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
複製代碼
  • mapGetters 輔助函數

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

import { mapGetters } from 'vuex'

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

//若是你想將一個 getter 屬性另取一個名字,使用對象形式
...mapGetters({
  // 把 `this.doneCount` 映射爲 `this.$store.getters.doneTodosCount`
  doneCount: 'doneTodosCount'
})
複製代碼
Mutations
  • 向 store.commit 傳入額外的參數,即 mutation 的 載荷(payload)

在大多數狀況下,載荷應該是一個對象,這樣能夠包含多個字段而且記錄的 mutation 會更易讀函數

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

store.commit('increment', {
  amount: 10
})

//對象風格的提交方式
store.commit({
  type: 'increment',
  amount: 10
})
複製代碼
  • 在組件中提交 Mutation

    • this.$store.commit('xxx')
    • 使用 mapMutations 輔助函數將組件中的 methods 映射爲 store.commit 調用
      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)`
          ]),
          ...mapMutations({
            add: 'increment' // 將 `this.add()` 映射爲 `this.$store.commit('increment')`
          })
        }
      }
      複製代碼
  • 注意事項

    • 最好提早在你的 store 中初始化好全部所需屬性。
    • 當須要在對象上添加新屬性時,你應該使用 Vue.set(obj, 'newProp', 123), 或者以新對象替換老對象。

      例如,利用對象展開運算符咱們能夠這樣寫: state.obj = { ...state.obj, newProp: 123 }

  • 使用常量替代 Mutation 事件類型

1.常量更容易避免程序出現錯誤。若是把一個值賦給程序中的一個常量,而該常量已經有一個值,編譯器就回報告錯誤。 2.在其多人協做的時候,方便統一管理,並且在其狀態管理當中調用方法時一眼就是看出是其mutation裏面的方法,有問題直接快速找到。

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'

// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
 state: { ... },
 mutations: {
   // 咱們可使用 ES2015 風格的計算屬性命名功能來使用一個常量做爲函數名
   [SOME_MUTATION] (state) {
     // mutate state
   }
 }
})
複製代碼
Actions
  • vs Mutations
    • Action 提交的是 mutation,而不是直接變動狀態。
    • Action 能夠包含任意異步操做
  • 基礎使用
actions: {
  increment ({ commit }) {
    commit('increment')
  }
}
複製代碼
  • 分發 Action
    store.dispatch('increment')
    // 以載荷形式分發
    store.dispatch('incrementAsync', {
      amount: 10
    })
    
    // 以對象形式分發
    store.dispatch({
      type: 'incrementAsync',
      amount: 10
    })
    複製代碼
  • 調用異步 API 和分發多重 mutation
    actions: {
      checkout ({ commit, state }, products) {
        // 把當前購物車的物品備份起來
        const savedCartItems = [...state.cart.added]
        // 發出結帳請求,而後樂觀地清空購物車
        commit(types.CHECKOUT_REQUEST)
        // 購物 API 接受一個成功回調和一個失敗回調
        shop.buyProducts(
          products,
          // 成功操做
          () => commit(types.CHECKOUT_SUCCESS),
          // 失敗操做
          () => commit(types.CHECKOUT_FAILURE, savedCartItems)
        )
      }
    }
    複製代碼
  • 在組件中分發 Action
    • 與mutations相似,使用 this.$store.dispatch('xxx') 分發 action
    • 使用 mapActions 輔助函數將組件的 methods 映射爲 store.dispatch 調用
    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
//利用 async / await,咱們能夠以下組合 action
// 假設 getData() 和 getOtherData() 返回的是 Promise

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}
複製代碼
相關文章
相關標籤/搜索