Vuex的五個核心概念—state/getter/mutation/action/module


1. State

Vuex 使用單一狀態樹——用一個對象包含全部的應用層級狀態。至此它就是“唯一數據源 (SSOT)”。這也意味着,每個應用將僅僅包含一個 store 實例。

組件中獲得 Vuex 狀態

  • 計算屬性

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

    然而,組件可能需要頻繁導入store

  • store選項

    //父組件
    import store from './store'
    import Counter from './Counter'
    new Vue({
      el: '#app',
      store,	//將store從根組件“注入”到每一個子組件中
      render:c=>c(Counter),
    })
    
    //子組件Counter.vue能通過 `this.$store` 訪問:
    <template>
    <div>{{count}}</div>
    </template>
    
    <script>
    export default {
        computed: {
        count () {
          return this.$store.state.count
        }
      }
    }
    </script>
    

輔助函數mapState

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

//store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex)

export default new Vuex.Store({
    state:{
        count:4
    }
})
//Counter.vue

<template>
    <div>{{sumCount}}</div>
</template>

<script>
import store from './store'
import {mapState} from 'vuex' //引入輔助函數
export default {
  data(){
    return{
      localCount:7
    }
  },
  computed:mapState({
        count:state=>state.count, //返回count,等同於count () {return this.$store.state.count}
        countAlias:'count', //count的別名是countAlias,這時候countAlias等同於count(注意單引號)

        sumCount(state){ //處理多狀態:store的數據加上本地的數據
          return state.count+this.localCount
        }
  })

}
</script>

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

computed: mapState([
  'count'  //等同於count:state=>state.count,
])

2. Getter

前言

假設我們需要處理store中state,例如對列表進行過濾並計數

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

然而如果很多組件都需要用到這個計算屬性,那么我們只能復制這個函數,或者頻繁導入它。

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

應用

1. 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接受第二個參數getter

    getters: {
      doneTodos: state => {
          return state.todos.filter(todo => todo.done)
      }
      doneTodosCount: (state, getters) => {
        return getters.doneTodos.length //通過getter訪問到doneTodos屬性
      }
    }
    

2. 通過屬性訪問

//訪問
store.getters.doneTodos 
store.getters.doneTodosCount 
//組件中使用
computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}

3. 通過方法訪問

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

getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}
//訪問
store.getters.getTodoById(2) //返回元素id與傳過去的id相等的元素

輔助函數mapGetters

import { mapGetters } from 'vuex'

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

3. Mutation

簡單示例

更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。 mutation 類似於事件:每個 mutation 都有一個 事件類型 (type) 和 一個 回調函數 (handler)。它接受 state 作為第一個參數:

const store = new Vuex.Store({
  state: {
    count: 1
  },
    
  mutations: {
    //事件類型:increment;回調函數為后面部分;
    increment (state) {
      state.count++
    }
  }
})

想要調用該事件類型的回調,需要使用store.commit(),並傳入字符串的事件類型

store.commit('increment')

提交載荷(Payload)

可以向 store.commit 傳入額外的參數,這些額外參數就是 mutation 的 載荷(payload)

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

在大多數情況下,載荷應該是一個對象,這樣更易讀:

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

提交方式

1. 沒有載荷

store.commit('事件類型')

2. 有載荷:

//一般寫法
store.commit('increment', 10)
//載荷為一個對象時
store.commit('事件類型', {
  amount: 10
})
//載荷為一個對象時-對象風格寫法
store.commit({
  type: '事件類型',
  amount: 10
})

3. 組件中提交

this.$store.commit('事件類型')

mapMutations輔助函數

methods:{
    //...
    ...mapMutations([
      'increment',  //相當於this.$store.commit('increment')
      'incrementBy', //相當於`this.$store.commit('incrementBy', n)`
      'incrementBy2' //相當於this.$store.commit('incrementBy2',{amount})
    ]),
    ...mapMutations({
      add:'increment' //此時this.add相當於$store.commit('increment')
    })
  }

注意:Mutation 必須是同步函數

使用常量代替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
    }
  }
})

4. Action

前言

Action 類似於 mutation,不同在於:

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

簡單示例

//一個往數組添加隨機數的實例:
const store = new Vuex.Store({
  state: {
    msg: []
  },
  mutations: {
    addMessage(state,msg){
      state.msg.push(msg)
    }
  },
  
//一個異步操作:
actions: {
     getMessages(context){
       fetch('https://www.easy-mock.com/mock/5f4a32907e1a7f3146e313e7/api/getnum')
         .then(res=>res.json())
         .then(data=>{
           context.commit('addMessage',data.data.number)
         })       
     }
 }
})

Action 函數接受一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以在這個對象中獲取 state 和 getters或提交一個 mutation。

ES6中可以通過參數解構簡化代碼

actions: {
     getMessages({commit}){
       //...
           commit('addMessage',data.data.number)
       //...      
     }
 }

分發Action

1. 普通分發

Action 通過 store.dispatch 方法觸發,

store.dispatch('getMessages')

2. 載荷分發

// 普通
store.dispatch('事件類型', {
  amount: 1
})

// 對象形式
store.dispatch({
  type: '事件類型',
  amount: 1
})

3. 組件中分發

this.$store.dispatch('事件類型')

mapMutations輔助函數

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'getMessages', // 相當於`this.$store.dispatch('getMessages')`
    ]),
    ...mapActions({
      addmsg: 'getMessages' //this.addmsg等同於`this.$store.dispatch('getMessages')`
    })
  }
}

組合Action

如何知道 action 什么時候結束呢?如何才能組合多個 action,以處理更加復雜的異步流程呢?首先要知道:

  • store.dispatch 可以處理被觸發的 action 的處理函數返回的 Promise
  • store.dispatch 仍舊返回 Promise:
actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

現在可以指定回調:

store.dispatch('actionA').then(() => {
  // ...
})

也可以 另一個action指定回調:

actions: {
  // ...
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

最后利用 async / await,我們可以如下組合 action:

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

5. Module

前言

當應用變得非常復雜時,store 對象就有可能變得相當臃腫。為了解決這個問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊:

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態

模塊的局部狀態

const moduleA = {
  state: () => ({
    count: 0
  }),
    
  //1. 對於mutation,接收的第一個參數是模塊的局部狀態對象。
  mutations: {
    increment (state) {
      state.count++
    }
  },
  //2. 對於getter,第一個參數暴露局部狀態;第三個參數`rootState`暴露根節點狀態
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
    
  //3. 對於action,局部狀態通過 context.state 暴露出來,根節點通過 context.rootState:
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

命名空間

默認情況下,模塊內部的 action、mutation 和 getter 是注冊在全局命名空間的——這樣使得多個模塊能夠對同一 mutation 或 action 作出響應。

如果希望你的模塊具有更高的封裝度和復用性,你可以在模塊對象中加入 namespaced: true 使其成為帶命名空間的模塊。當模塊被注冊后,它的所有 getter、action 及 mutation 都會自動根據模塊注冊的路徑調整命名

const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true,
      state: () => ({ ... }), 
      getters: {isAdmin () { ... }},// 通過 store.getters['account/isAdmin']訪問
      actions: {login () { ... }},// 通過 store.dispatch('account/login')分發
      mutations: {login () { ... }},//通過store.commit('account/login')提交

      //模塊中嵌套模塊-繼承父模塊的命名空間
      modules: {
        //模塊1-myPage
        myPage: {
          state: () => ({ ... }),
          getters: {profile () { ... }}//store.getters['account/profile']
        },

        //模塊2-mposts-進一步嵌套命名空間
        posts: {
          namespaced: true,
          state: () => ({ ... }),
          getters: {popular () { ... }}//store.getters['account/posts/popular']
        }
      }
    }
  }
})

在帶命名空間的模塊內訪問全局內容

rootStaterootGetters 會作為第三和第四參數傳入 getter,也會通過 context 對象的屬性傳入 action。

若需要在全局命名空間內分發 action 或提交 mutation,將 { root: true } 作為第三參數傳給 dispatchcommit 即可。

modules: {
  foo: {
    namespaced: true,
    getters: {
      someOtherGetter: state => { ... }
      //1. 對於getter,使用第四個參數 `rootGetters`訪問根getter
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 局部的
        rootGetters.someOtherGetter // -> 全局的
      },
    },

    actions: {
      someOtherAction (ctx, payload) { ... }
	  //2. 對於actions,接受 root 屬性來訪問根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        dispatch('someOtherAction') // -> 局部的分發
        dispatch('someOtherAction', null, { root: true }) // -> 全局的分發

        commit('someMutation') // -> 局部的提交
        commit('someMutation', null, { root: true }) // -> 全局的提交
      },
    }
  }
}

在帶命名空間的模塊注冊全局 action

在action中添加 root: true,並將這個 action 的定義放在函數 handler 中。例如:

{
  //...
  modules: {
    foo: {
      namespaced: true,
      actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> 定義someAction action
        }
      }
    }
  }
}

帶命名空間的綁定函數

當使用 mapState, mapGetters, mapActionsmapMutations 這些函數來綁定帶命名空間的模塊時:

computed: {
  ...mapState({
    a: state => state.some.nested.module.a,
    b: state => state.some.nested.module.b
  })
},
methods: {
  ...mapActions([
    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ])
}

你可以將模塊的空間名稱字符串作為第一個參數傳遞給上述函數,這樣所有綁定都會自動將該模塊作為上下文,簡化后:

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}

簡化的另一個方法:使用 createNamespacedHelpers 創建基於某個命名空間輔助函數。它返回一個對象,對象里有新的綁定在給定命名空間值上的組件綁定輔助函數:

import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')//基於某個命名

export default {
  computed: {
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

實例源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM