vuex中的Store(倉庫)


vuex中的Store

1. Vuex是什么
在了解Store之前,我們先來看看Vuex是個什么東西。Vuex本質上就是一個Vue.js的插件,是用於對復雜應用進行狀態管理用的,打印Vuex以后輸出:

{

​ Store: function Store(){},

​ mapActions: function(){}, // 對應Actions的結果集

​ mapGetters: function(){}, // 對應Getters的結果集

​ mapMutations: function(){}, // 對應Mutations的結果集

​ mapState: function(){}, // 對應State的結果集

​ install: function install(){},

​ installed: true

}
Vuex和單純的全局對象有以下兩點不同:

Vuex的狀態存儲是響應式的。當Vue 組件從 Store 中讀取狀態的時候,若 Store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新。
不能直接改變 Store 中的狀態。改變Store 中的狀態的唯一途徑就是顯式地提交 mutation。
2. Store
每一個Vuex應用的核心就是Store(倉庫),我們可以說Store是一個容器,Store里面的狀態與單純的全局變量是不一樣的,無法直接改變Store中的狀態。想要改變Store中的狀態,只有一個辦法,顯式地提交mutation。

3. 一個完整的Store結構
const store = new Vuex.Store({
state: {
// 存放狀態
},
getters: {
// state的計算屬性
},
mutations: {
// 更改state中狀態的邏輯,同步操作
},
actions: {
// 提交mutation,異步操作
},
// 如果將store分成一個個的模塊的話,則需要用到modules。
//然后在每一個module中寫state, getters, mutations, actions等。
modules: {
a: moduleA,
b: moduleB,
// ...
}
});
4. 狀態管理的幾個核心概念
state
state是狀態數據,可以通過this.$store.state來直接獲取狀態,也可以利用vuex提供的mapState輔助函數將state映射到計算屬性(computed)中去。用data接收的值不能及時響應更新,用computed就可以:

export default {
data () {
return {
dataCount: this.$store.state.count //用data接收
}
},
computed:{
count(){
return this.$store.state.count //用computed接收
}
}
}
mapState 輔助函數:

mapState是state的語法糖,當一個組件需要獲取多個狀態時候,將這些狀態都聲明為計算屬性會有些重復和冗余。為了解決這個問題,我們可以使用 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'
])
2.getter

getters本質上是用來對狀態進行加工處理。Getters與State的關系,就像Vue.js的computed與data的關系。getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變才會被重新計算。可以通過this.$store.getters.valueName對派生出來的狀態進行訪問。或者直接使用輔助函數mapGetters將其映射到本地計算屬性中去。

mapGetters 輔助函數:

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

import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用對象展開運算符將 getter 混入 computed 對象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
mapGetters實際上是一個方法Vuex對象上的一個方法,這從本文開頭打印的那個Vuex對象的內容可以看出來。…這個符號是ES2015的一個新的語法糖,即將mapGetters處理后的內容展開后填入。

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

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

mutations的中文意思是“變化”,利用它可以更改狀態。事實上,更改 Vuex 的 store 中的狀態的唯一方法就是提交 (commit)mutation。不過,mutation觸發狀態改變的方式有一點特別,所謂commit一個mutation,實際是像觸發一個事件一樣,傳入一個mutation的類型以及攜帶一些數據(稱作payload,載荷)。

mutations: { //放置mutations方法
increment(state, payload) {
//在這里改變state中的數據
state.count = payload.number;
}
},
那commit一個mutation在代碼層面怎么表示呢?

this.$store.commit('increment', {
amount: 10
})
//或者這樣
this.$store.commit({
type: 'increment',
amount: 10
})
除了這種使用 this.$store.commit('xxx') 提交 mutation的方式之外,還有一種方式,即使用 mapMutations 輔助函數將組件中的 methods 映射為 this.$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')`
})
}
}
經過這樣的映射之后,就可以通過調用方法的方式來觸發其對應的(所映射到的)mutation commit了,比如,上例中調用add()方法,就相當於執行了this.$store.commit('increment')了。

考慮到觸發的mutation的type必須與mutations里聲明的mutation名稱一致,比較好的方式是把這些mutation都集中到一個文件(如mutation-types)中以常量的形式定義,在其它地方再將這個文件引入,便於管理。而且這樣做還有一個好處,就是整個應用中一共有哪些mutation type可以一目了然。就像下面這樣:

// 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中可以執行store.commit,而且action中可以有任何的異步操作:

const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
或者用ES2015的參數解構,可以簡寫成:

actions: {
increment ({commit}) {
commit('increment')
}
}
和mutation類似,我們像上面這樣生命action的處理函數。它接收的第一個參數是一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以調用 context.commit 提交一個 mutation,或者通過 context.state 和 context.getters 來獲取 state 和 getters。

不過,mutation處理函數中所做的事情是改變state,而action處理函數中所做的事情則是commit mutation。

怎么觸發action呢?按照Vuex的叫法,這叫分發(dispatch),我們反正知道它實際上是觸發的意思就行了。具體的觸發方法是this.$store.dispatch(actionType, payload)。所傳的兩個參數一個是要觸發的action的類型,一個是所攜帶的數據(payload),類似於上文所講的commit mutation時所傳的那兩個參數。具體如下:

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



// 以對象形式分發
this.$store.dispatch({
type: 'incrementAsync',
amount: 10
})
還有一種方法是使用 mapActions 輔助函數將組件的 methods 映射為 this.$store.dispatch 調用。如下:

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')`
})
}
}
另外,還需要知道, this.$store.dispatch 可以處理被觸發的 action 的處理函數返回的 Promise,並且 this.$store.dispatch 仍舊返回 Promise。

再來看一些組合性的異步操作:

actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
現在你可以:

$this.store.dispatch('actionA').then(() => {
// ...
})
在另外一個 action 中也可以:

actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
最后,如果我們利用 async / await 這個 JavaScript 即將到來的新特性,我們可以像這樣組合 action:

// 假設 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
接着來看一個更加實際的購物車示例,涉及到調用異步 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)
)
}
}
5.module

module是對於store的一種切割。由於Vuex使用的是單一狀態樹,這樣整個應用的所有狀態都會集中到一個比較大的對象上面,那么,當應用變得非常復雜時,store 對象就很可能變得相當臃腫!它解決了當state中很臃腫的時候,module可以將store分割成模塊,每個模塊中擁有自己的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 的狀態
模塊的局部狀態
對於每個模塊內部的 mutation 和 getter,接收的第一個參數就是模塊的局部狀態對象。

const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
// 這里的 `state` 對象是模塊的局部狀態
state.count++
}
},

getters: {
doubleCount (state) {
return state.count * 2
}
}
}
同樣,對於模塊內部的 action,局部狀態通過 context.state 暴露出來,根節點狀態則為 context.rootState:

const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
對於模塊內部的 getter,根節點狀態會作為第三個參數暴露出來:

const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
命名空間
默認情況下,模塊內部的 action、mutation 和 getter 是注冊在全局命名空間的——這樣使得多個模塊能夠對同一 mutation 或 action 作出響應。

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

const store = new Vuex.Store({
modules: {
account: {
namespaced: true,

// 模塊內容(module assets)
state: { ... }, // 模塊內的狀態已經是嵌套的了,使用 `namespaced` 屬性不會對其產生影響
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},

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

// 進一步嵌套命名空間
posts: {
namespaced: true,

state: { ... },
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
啟用了命名空間的 getter 和 action 會收到局部化的 getter,dispatch 和 commit。

在命名空間模塊內訪問全局內容(Global Assets)

如果你希望使用全局 state 和 getter,rootState 和 rootGetter 會作為第三和第四參數傳入 getter,也會通過 context 對象的屬性傳入 action。

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

modules: {
foo: {
namespaced: true,

getters: {
// 在這個模塊的 getter 中,`getters` 被局部化了
// 你可以使用 getter 的第四個參數來調用 `rootGetters`
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
},
someOtherGetter: state => { ... }
},

actions: {
// 在這個模塊中, dispatch 和 commit 也被局部化了
// 他們可以接受 `root` 屬性以訪問根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'

dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
帶命名空間的綁定函數
當使用 mapState, mapGetters, mapActions 和 mapMutations 這些函數來綁定命名空間模塊時,寫起來可能比較繁瑣:

computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
'some/nested/module/foo',
'some/nested/module/bar'
])
}
對於這種情況,你可以將模塊的空間名稱字符串作為第一個參數傳遞給上述函數,這樣所有綁定都會自動將該模塊作為上下文。於是上面的例子可以簡化為:

computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo',
'bar'
])
}
而且,你可以通過使用 createNamespacedHelpers 創建基於某個命名空間輔助函數。它返回一個對象,對象里有新的綁定在給定命名空間值上的組件綁定輔助函數:

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
給插件開發者的注意事項
如果你開發的插件(Plugin)提供了模塊並允許用戶將其添加到 Vuex store,可能需要考慮模塊的空間名稱問題。對於這種情況,你可以通過插件的參數對象來允許用戶指定空間名稱:

// 通過插件的參數對象得到空間名稱
// 然后返回 Vuex 插件函數
export function createPlugin (options = {}) {
return function (store) {
// 把空間名字添加到插件模塊的類型(type)中去
const namespace = options.namespace || ''
store.dispatch(namespace + 'pluginAction')
}
}
模塊動態注冊
在 store 創建之后,你可以使用 store.registerModule 方法注冊模塊:

// 注冊模塊 `myModule`
store.registerModule('myModule', {
// ...
})
// 注冊嵌套模塊 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
之后就可以通過 store.state.myModule 和 store.state.nested.myModule 訪問模塊的狀態。

模塊動態注冊功能使得其他 Vue 插件可以通過在 store 中附加新模塊的方式來使用 Vuex 管理狀態。例如,vuex-router-sync 插件就是通過動態注冊模塊將 vue-router 和 vuex 結合在一起,實現應用的路由狀態管理。

你也可以使用 store.unregisterModule(moduleName) 來動態卸載模塊。注意,你不能使用此方法卸載靜態模塊(即創建 store 時聲明的模塊)。

模塊重用
有時我們可能需要創建一個模塊的多個實例,例如:

(1)創建多個 store,他們公用同一個模塊

(2)在一個 store 中多次注冊同一個模塊

如果我們使用一個純對象來聲明模塊的狀態,那么這個狀態對象會通過引用被共享,導致狀態對象被修改時 store 或模塊間數據互相污染的問題。

實際上這和 Vue 組件內的 data 是同樣的問題。因此解決辦法也是相同的——使用一個函數來聲明模塊狀態(僅 2.3.0+ 支持):

const MyReusableModule = {
state () {
return {
foo: 'bar'
}
},
// mutation, action 和 getter 等等...
}
5. store與$store的區別
$store 是掛載在 Vue 實例上的(即Vue.prototype),組件也是一個Vue實例,在組件中可使用 this 訪問原型上的屬性。template 中可直接通過 {{ $store.state.userName }} 訪問,等價於 script 中的 this.$store.state.userName。
至於 {{ store.state.userName }},script 中的 data 需聲明過 store 才可訪問。

 

總之,有以下要注意的:

(1)在功能上:

state保存的是數據

getters是對state進行二次加工

action的處理函數的功能最終是commit mutation

mutation處理函數的功能最終是改變state

(2)在流程上:

vue component—-dispatch—->actions—-commit—->mutations—-mutate—->state—-render—->vue component。從而形成閉環。

(3)輔助方法的映射上:

mapGetters、mapState 都是用在computed聲明里面;

mapActions、mapMutations則都是用在methods聲明里面。
————————————————
版權聲明:本文為CSDN博主「stormzi」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/stormzi/article/details/107159290


免責聲明!

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



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