學前準備
本文主要內容來源于官網,為Vuex的基礎部分總結,部分案例也參考了互聯網上其他分享知識的大佬。本手記結合官網內容也額外添加了自己的一些的理解,希望能給你帶來一些參考價值,如果文章有理解不到位的地方,還請各位多批評指正!以下是本文中可能用到的參考資料:
點擊進入vuex官方教程
為什么使用Vuex
Vuex的官方解答:Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。
在vue開發的過程中,我們經常遇到一個狀態可能會在多個組件之間使用,比如用戶信息、頭像、昵稱等,我們修改了這些信息,那么整個應用中凡是使用了該信息的部分都應該更新。想讓vue
中所有組件都共享這些信息,那么就需要將這些信息進行集中管理,這個時候我們就需要用到Vuex
。
通過Vue CLI生成項目模板
日常開發中,我們大多都是用Vue CLI
腳手架來生成一個vue項目,不太清楚腳手架怎么使用的可以移步Vue CLI官網自行查閱。在使用腳手架生成的項目時會讓我們選擇store
,選擇后會在頁面中給我們生成store
文件夾,自帶初始化倉庫的index.js
,這就是最初的store
,里面結構如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {}
})
在模板中我們可以看到state
、mutations
、actions
、modules
這幾個對象,而Vuex
中幾乎所有的操作都是圍繞它們展開的,接下來我們就來逐一認識它們。
State
Vuex
的核心倉庫是store
,這個store
實例會被注入到所有的子組件里面,而所有子組件中公用的數據信息和狀態就都存儲在State
里面。我們來看一個簡單的小栗子。
export default new Vuex.Store({
state: {
count: 102
}
})
vuex
中所有的狀態管理都會定義在 state
中,我們在 state
中定義了 count
,那么我們就得到了一個集中管理的狀態 count
,所有的組件都可以直接訪問到這個 count
。
<template>
<div class="index">
{{count}}
</div>
</template>
<script>
export default {
computed: {
count () {
return this.$store.state.count
}
}
}
</script>
因為根實例中注冊 store
選項,該 store
實例會注入到根組件下的所有子組件中,且子組件能通過 this.$store
訪問到。通過計算屬性,我們就可以在模板里面使用模板語法來調用count
了,當然我們也可以在模板中的直接寫入。有小伙伴會好奇為什么不能寫在data
里或其它地方嗎,state
寫在計算屬性中是因為模板文件需要響應倉庫中state
的變化,而state
變化之后如果寫在data
中就不能及時響應渲染到頁面中,而computed
身為計算屬性會監聽函數下面值得變化然后進行重新渲染。
<template>
<div class="index">
{{this.$store.state.count}}
</div>
</template>
-
mapState
如果我們在store
中存儲了很多個狀態,而在當前模板頁面中又要讀取store
里面大量狀態,那么我們在當前頁面采用computed
中來return store
里面的狀態這種寫法就會變得很冗余。Vuex
中針對state
給我們提供了它的語法糖mapState
,我們可以通過mapState()
方法直接獲取state中存儲的狀態,如下栗子:
<template>
<div class="index">
{{count}}
{{total}}
</div>
</template>
<script>
// 使用該語法糖我們需提前引入
import { mapState } from 'vuex'
export default {
computed: {
total () {
return 10 + this.count
},
...mapState(['count']) // 如需引入多個狀態可寫成...mapState(['count', 'name', 'six'])
}
}
</script>
// 102
// 112
Getter
有時候我們需要從 store
中的 state
中派生出一些狀態,例如如果state
中有一個數組,我們要將數組中所有大于10的數字挑選出來然后在給組件使用,栗子如下:
<template>
<div class="index">
{{modiyArr}}
</div>
</template>
<script>
export default {
computed: {
modiyArr () {
return this.$store.state.list.filter(item => item > 10)
}
}
}
</script>
// [20, 40, 50, 66, 77, 88]
我們在computed
中完成了代碼邏輯,同時也暴露出了一些問題,就是如果我們需要在多個組件中都完成這個數組的過濾,就需要在各個組件處都復制這段代碼或者將其抽取到一個共享函數然后在多處導入它,無論哪一種方式都不是很理想。這個時候我們就可以使用vuex
中的getter
。
Vuex
允許我們在 store
中定義“getter
”(可以認為是 store
的計算屬性)。就像計算屬性一樣,getter
的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變才會被重新計算。
<!-- 使用Getter的第一種用法 -->
<template>
<div class="index">
{{this.$store.getters.modiyArr}}
</div>
</template>
<!-- 使用Getter的第二種用法 -->
<template>
<div class="index">
{{modiyArr}}
{{getLength}}
</div>
</template>
<script>
export default {
computed: {
modiyArr () {
return this.$store.getters.modiyArr
},
getLength () {
return this.$store.getters.getLength
}
}
}
</script>
// stoer.js
export default new Vuex.Store({
state: {
list: [1, 3, 4, 10, 20, 40, 50, 66, 77, 88]
},
getters: {
// getters中的函數接收兩個參數(state, getters)
modiyArr(state) {
return state.list.filter(item => item > 10)
},
getLength(state, getters) {
return getters.modiyArr.length // 可以通過getters直接調用它的其它方法
}
}
})
// [20, 40, 50, 66, 77, 88]
上面的栗子基本都是通過屬性來訪問的,使用getters
也可以支持通過方法來訪問,我們來看一個官網的小栗子
// 篩選state中的額todos,將todos數組中id為2的對象找出來
// store.js
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
})
<template>
<div class="index">
{{checkDoneTodos}}
</div>
</template>
<script>
import { mapState, mapGetter } from 'vuex'
export default {
computed: {
checkDoneTodos () {
return this.$store.getters.doneTodos(2)
}
}
}
</script>
// { id: 2, text: '...', done: false }
注意,
getter
在通過屬性訪問時是作為Vue
的響應式系統的一部分緩存其中的。getter
在通過方法訪問時,每次都會去進行調用,而不會緩存結果。
-
mapGetters
mapGetters
也是Vuex
幫我們封裝好的語法糖,具體用法其實和mapState
差不多。
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用對象展開運算符將 getter 混入 computed 對象中
...mapGetters([
'modiyArr',
'getLength',
// ...
])
}
}
當然...mapGetters
本身也是支持對象類寫法的
...mapGetters({
// 把 `this.editArr` 映射為 `this.$store.getters.modiyArr`
editArr: 'modiyArr',
getLength: 'getLength'
})
Mutation
如果我們需要對store.js
中的狀態進行修改,我們是不能在組件里面直接修改的,因為組件里面直接修改倉庫是不會進行全局響應的,這就違背了我們使用倉庫的初衷。唯一的方法就是通過提交mutation
來進行修改,這樣倉庫中與之對應的狀態也會被修改進而影響全部組件的數據。Vuex
中的 mutation
非常類似于事件:每個 mutation
都有一個字符串的 事件類型 (type)
和 一個 回調函數 (handler)
。這個回調函數就是我們實際進行狀態更改的地方,并且它會接受 state
作為第一個參數:
<template>
<div class="index">
<button @click="add">增加</button>
{{num}}
<button @click="reduce">減少</button>
</div>
</template>
<script>
import { mapState, mapGetter } from 'vuex'
export default {
computed: {
...mapState(['num'])
},
methods: {
add() {
this.$store.commit('add')
},
reduce () {
this.$store.commit('reduce')
}
}
}
</script>
export default new Vuex.Store({
state: {
num: 10
},
mutations: {
add (state) {
// 變更狀態
state.num++
},
reduce (state) {
state.num--
}
}
})
上面栗子中,我們在state
中定義了num
,然后通過mutations
添加add
方法和reduce
方法去對num
進行修改,在模板文件中我們定義了click
事件來改變num
的值,模板文件中的事件去響應store
中的mutations
主要是通過commit()
來實現的。
-
提交載荷(Payload)
你可以向store.commit
傳入額外的參數,即 mutation 的 載荷(payload)
,其實換種方式理解可能更容易,就是模板文件中的commit
可以添加額外的參數,mutations
中接收的時候接收一個形參payload
,就代表模板文件中你傳進來的參數。vuex
官網更建議我們payload
應該是一個對象,這樣可以包含多個字段并且記錄的 mutation
會更易讀,我們看下面的小栗子:
export default {
methods: {
add() {
//this.$store.commit('add', 100)直接傳遞額外參數,不建議,更建議下一種對象的寫法
this.$store.commit('add', {
addNum: 100
})
}
}
}
export default new Vuex.Store({
state: {
num: 10
},
mutations: {
add (state, payload) {
state.num+=payload.addNum
},
reduce (state) {
state.num--
}
}
})
也可以將所有參數寫到一個對象里面,那么type
就對應mutations
中要執行的函數名
export default {
methods: {
add() {
this.$store.commit({
type: 'add',
addNum: 100
})
}
}
}
-
Mutation 需遵守 Vue 的響應規則
這個通俗點說就是你在開發過程中需要向state
里面添加額外數據時,需要遵循響應準則。官方文檔說既然 Vuex
的 store
中的狀態是響應式的,那么當我們變更狀態時,監視狀態的 Vue
組件也會自動更新。這也意味著 Vuex
中的 mutation
也需要與使用Vue
一樣遵守一些注意事項: 1.最好提前在你的 store
中初始化好所有所需屬性。 2.當需要在對象上添加新屬性時,你應該使用Vue.set(obj, 'newProp', 123)
,或者以新對象替換老對象。例如,利用對象展開運算符我們可以這樣寫:
state.obj = { ...state.obj, newProp: 123 }
文字很枯燥,我們還是來看個小栗子
<template>
<div class="index">
<button @click="add">增加</button>
{{num}}
// add方法執行之后頁面會立即響應這個新的狀態
<div class="new-num">{{this.$store.state.newNum || 0}}</div>
</div>
</template>
mutations: {
add (state, payload) {
state.num+=payload.addNum
Vue.set(state, 'newNum', 1000) // 像倉庫中的state新增加一個newNum狀態,初始值為1000
// state= {...state, newNum: 2000} 這個方法不管用了,用下面的replaceState()方法
this.replaceState({...state, newNum: 2000})
}
}
-
Mutation 必須是同步函數
下面這種寫法必須避免(直接官方例子加持):
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
我們在模板文件中通過事件去操作mutations
時,如果mutations
中為異步函數,那么當 mutation
觸發的時候,回調函數還沒有被調用,因為我們不知道什么時候回調函數實際上被調用——實質上任何在回調函數中進行的狀態的改變都是不可追蹤的。
-
mapMutations
其實這幾個語法糖的使用方法都差不多,這里就直接上官方栗子了。
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')`
})
}
}
Action
Action
類似于mutation
,不同在于:
-
Action
提交的是mutation
,而不是直接變更狀態。 -
Action
可以包含任意異步操作。前面說過mutation
只能包含同步事務,所以在處理異步事務就需要Action
,通過Action
控制了異步這一過程,之后再去調用mutation
里面的方法來改變狀態。
先看官方的一個小栗子來認識它的基礎語法:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Action
函數接受一個與 store
實例具有相同方法和屬性的 context
對象,因此你可以調用 context.commit
提交一個 mutation
,或者通過 context.state
和 context.getters
來獲取 state
和 getters
。那為什么這里的參數不能直接是store
本身呢,這就和Modules
有關了,了解Modules
之后就會發現store
總倉庫下面可能會有很多個不同的模塊倉庫,而每一個不同的模塊倉庫都有自己的Action
、state
、mutation
、getter
,如果使用store
那么store.state
拿到的就不會是當前模塊的state
,而context
可以理解為當前模塊的store
,這樣就不會引起沖突。
實踐中,我們會經常用到 ES2015 的 參數解構 來簡化代碼(特別是我們需要調用 commit 很多次的時候):
actions: {
increment ({ commit }) { // 這里就是將context展開式寫法 { commit } = context.commit
commit('increment')
}
}
-
分發 Action
Action
通過 store.dispatch
方法觸發:
store.dispatch('increment')
乍一眼看上去感覺多此一舉,我們直接分發 mutation
豈不更方便?實際上并非如此,還記得 mutation
必須同步執行這個限制么?Action
就不受約束!我們可以在 Action
內部執行異步操作。
上面這一塊內容基本來源于官網,感覺官網對于 Action
函數的傳參,分發等基本用法都說的比較詳細,在這里我們只要記住一點Action
用來執行異步操作倉庫狀態的情況。我們還是來做個小栗子更直觀了解吧
<template>
<div class="index">
<button @click="add">增加</button>
{{num}}
</div>
</template>
<script>
import { mapState, mapGetter } from 'vuex'
export default {
computed: {
...mapState(['num'])
},
methods: {
add() {
this.$store.dispatch('delayAdd')
}
}
}
</script>
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num: 10
},
mutations: {
add (state, addNum) {
state.num += addNum
}
},
actions: {
delayAdd (context) {
setTimeout(() => {
context.commit('add', 100)
}, 1000)
}
}
})
上面是一個Action中
最基礎的用法,因為Mutation中
不能接受異步操作,所以我們先將要修改的東西放在Mutation
中,然后在Action
中等待1s之后去操作Mutation
進行提交來更新數據。至于分發Action
是我們在模板中通過 this.$store.dispatch('delayAdd')
來執行,這樣我們就完成了一個基礎的Action
閉環demo
。
-
Actions 支持同樣的載荷方式和對象方式進行分發:
// 以載荷形式分發
store.dispatch('delayAdd', {
addNum: 100
})
actions: {
delayAdd (context, payload) {
setTimeout(() => {
context.commit('add', payload.addNum)
}, 1000)
}
}
// 以對象形式分發
store.dispatch({
type: 'delayAdd',
addNum: 100
})
這里copy一個官方購物車示例,涉及到調用異步 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)
)
}
}
注意我們正在進行一系列的異步操作,并且通過提交 mutation
來記錄 action
產生的副作用(即狀態變更)。
-
mapActions
Action
也有類似的語法糖,這里就不多贅述了,其實用法都是差不多的,直接官方栗子
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結合Promise
Action
通常是異步的,那么如何知道 Action
什么時候結束呢?我們這里假設一種情況,模板上有一個數據,同時有這個數據當前的狀態說明,我們如果通過Action
去操作這個數據,那么數據改變之后我們希望這個數據的當前狀態說明就馬上更新。舉個小栗子
<template>
<div class="index">
<div class="index-name">姓名:{{userinfo.name}}</div>
<div class="index-six">性別:{{userinfo.six}}</div>
<div class="number-hint">狀態:{{defalutText}}</div>
<div class="click-btn" @click.stop="handleClickChange">
<button>{{buttonText}}</button>
</div>
</div>
</template>
<script>
import { mapState, mapGetter, mapAction } from "vuex";
export default {
data () {
return {
defalutText: "信息暫未修改" ,
buttonText: "點擊改變信息",
time: 5
}
},
computed: {
...mapState(["userinfo"]),
},
methods: {
handleClickChange () {
this.changeButton()
this.$store.dispatch({
type: 'changeInfo',
name: '李四',
six: '女',
time: this.time
}).then(res => {
console.log(res) // 執行完畢,可以開始改變狀態啦
this.defalutText = '信息更新成功'
})
},
changeButton () {
this.buttonText = this.time + 's后信息將被改變'
let t = setInterval(() => {
this.time--
if (this.time != 0) {
this.buttonText = this.time + 's后信息將被改變'
} else {
this.buttonText = '成功改變啦'
clearInterval(t)
}
}, 1000)
},
},
};
</script>
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userinfo: {
name: '張三',
six: '男'
}
},
mutations: {
changeInfo (state, payload) {
state.userinfo.name = payload.name
state.userinfo.six = payload.six
}
},
actions: {
// 這里直接用參數解構的寫法
changeInfo ({commit, state}, payload) { // 接收context(解構寫法)和載荷payload
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('changeInfo', {
name: payload.name,
six: payload.six
})
resolve("執行完畢,可以開始改變狀態啦") // 成功之后即可直接在then的回調中接收
}, payload.time * 1000)
})
}
}
})
上面的代碼基本就是先展示state
中存好的userinfo
信息,點擊按鈕之后會有一個time
的倒計時來告訴你幾秒之后改變數據,改變之后再將文字改成"成功改變啦"。
其實上面這段代碼是有小瑕疵的,因為我們在分發action
的changeInfo
函數中給的是一個定時器,沒有任何延遲信息,所以5s之后返回來的參數和我們在changeButton()
中寫好的改變button
狀態的文字能正好對應上。而日常開發中通常這里會寫請求函數到后端然后等待返回值,這個過程如果耽誤1s
那么們changeButton()
中就會出現問題,它會優先執行this.buttonText = '成功改變啦'
。
而實際因為請求有時間延遲,可能多出1s
就會出現文字改變而上面的信息并未更新。這里特意留給我們自己思考,如何完善這個小bug
,其實答案很簡單,就是在then()
的回調中去處理這段邏輯,具體的實現可以自行去了解哦,對Promise
不太了解的可以點擊了解Promise。
-
組合Action
利用Promise來實現組和Action
我們先直接上官網的demo小栗子,通過栗子看更明白
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
其實上面的這個小栗子在我們上一塊 Action
結合Promise
中就說的很明白了,它就是想告訴我們通過Promise.resolve()
之后我們就知道這個時候actionA
就執行完畢了,我們就可以通過鏈式調用的then()
方法來走下一個流程啦。看官方給的actionB
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
vue
模板中調用
store.dispatch('actionA').then((res) => {
// ... 這里執行actionA中異步結束之后的程序,可以接受res為actionA中resolve("將返回結果返回出去")
})
利用 async / await來實現組和Action
async
和await
是ES7中推出的新語法,直接看官方的栗子,簡單明了
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
一個 store.dispatch 在不同模塊中可以觸發多個 action 函數。在這種情況下,只有當所有觸發函數完成后,返回的 Promise 才會執行。
Module
由于使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常復雜時,store
對象就有可能變得相當臃腫。
為了解決以上問題,Vuex
允許我們將 store
分割成模塊(module
)。每個模塊擁有自己的state
、mutation
、action
、getter
、甚至是嵌套子模塊——從上至下進行同樣方式的分割,我們先來做個簡單的栗子,這里我們就不直接在index.js
里面新建module
了,因為實際開發中我們也是將模塊單獨拎出來,這樣更有利于代碼維護。如下栗子:
// moduleA.js
const moduleA = {
state: {
userInfo: {
name: 'alen',
age: 20
}
},
mutations: { ... },
getters: { ... },
actions: { ... }
}
export default moduleA
// moduleB.js
const moduleB = {
state: {
userInfo: {
name: 'nemo',
age: 32
}
},
mutations: { ... },
getters: { ... },
actions: { ... }
}
export default moduleB
在store
倉庫的跟文件index.js
中引入模塊A和模塊B
import Vue from 'vue'
import Vuex from 'vuex'
import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'
Vue.use(Vuex)
export default new Vuex.Store({
state: { ... },
modules: {
ma: moduleA,
mb: moduleB
}
})
上面的代碼就讓我們輕松完成了模塊的新建和引入,如果我們想在組件里訪問模塊A中的name
:
<template>
<div class="index">
<div class="modulea-name">{{this.$store.state.ma.userInfo.name}}</div> // alen
<div class="moduleb-name">{{this.$store.state.mb.userInfo.name}}</div> // nemo
</div>
</template>
上面的代碼我們不難猜出,起始ma
和mb
的state
都是掛載在根節點的state
下面。所以我們在組件中直接訪問模塊中的state
需要先去訪問根節點的state
然后在加上模塊名及對應的參數。
-
模塊的局部狀態
對于模塊內部的 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: {
// 這里用的解構寫法,實際上是context.state, context.commit, context.rootState
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
}
}
}
這里我們直接舉一個組件操作模塊B進行mutation
提交的小栗子
<template>
<div class="index">
<div class="moduleb-name">{{this.$store.state.mb.userInfo.name}}</div>
<div class="moduleb-age">{{this.$store.state.mb.userInfo.age}}</div>
<button @click="handleClickChangeAge(60)">修改年齡</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions } from "vuex";
export default {
methods: {
handleClickChangeAge(newAge) {
this.$store.commit({
type: 'editAge',
age: newAge
})
}
}
}
</script>
// moduleB.js
const moduleB = {
state: {
userInfo: {
name: 'nemo',
age: 32,
hobby: ['football', 'basketball', 'badminton', 'volleyball']
}
},
mutations: {
editAge (state, payload) {
state.userInfo.age = payload.age
}
}
}
export default moduleB
看上面的代碼會發現我們通過 commit
提交模塊B
里面的內容并沒有使用 mb
這個模塊名,而是直接全局提交就能進行修改,我們再來看看 getters
是不是也是直接可以全局提交修改。
<template>
<div class="index">
<div class="moduleb-hobby">
<div v-for="(item, index) in hobby" :key="index">{{item}}</div>
</div>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions } from "vuex";
export default {ddd
computed: {
modeulaName () {
console.log(this.$store.state.ma.userInfo.name)
},
filteHobby () {
console.log(this.$store.getters.filteHobby)
return this.$store.getters.filteHobby
}
// 使用語法糖來寫
// ...mapGetters({
// hobby: 'filteHobby'
// })
}
};
</script>
const moduleB = {
state: {
userInfo: {
name: 'nemo',
age: 32,
hobby: ['football', 'basketball', 'badminton', 'volleyball']
}
},
getters: {
filteHobby (state, getters, rootState) {
return state.userInfo.hobby.filter(item => item != 'football')
}
}
}
export default moduleB
果然 getters
也可以直接通過 this.$store.getters
來操作,而不需要再加上它所在的模塊名來進行調用,這里我們來看看官方是這樣說的:
默認情況下,模塊內部的
action
、mutation
和getter
是注冊在全局命名空間的——這樣使得多個模塊能夠對同一mutation
或action
作出響應。
-
命名空間
如果希望你的模塊具有更高的封裝度和復用性,你可以通過添加 namespaced: true
的方式使其成為帶命名空間的模塊。當模塊被注冊后,它的所有 getter
、action
及 mutation
都會自動根據模塊注冊的路徑調整命名。直接來看小栗子:
// 新建模塊C
const moduleC = {
namespaced: true,
state: {
userInfo: {
name: 'kity',
age: 10,
list: [1, 2, 3, 4]
}
},
getters: {
filterList (state) {
return state.userInfo.list.filter(item => item != 1)
}
},
mutations: {
changeAge (state, payload) {
state.userInfo.age = payload.age
}
},
actions: {...}
}
export default moduleC
// index.js中導入
import moduleC from './modules/moduleC'
export default new Vuex.Store({
modules: {
mc: moduleC
}
})
// 模板文件
<template>
<div class="index">
<div class="modulec-name">{{this.$store.state.mc.userInfo.name}}</div>
<div class="modulec-age">{{this.$store.state.mc.userInfo.age}}</div>
<div class="modulec-list">
<div v-for="(item, index) in list" :key="index">{{item}}</div>
</div>
<button @click="handleClickChangeAge(20)">修改年齡</button>
</div>
</template>
通過 commit
提交 moduleC
中的 changeAge()
,這時候的寫法如下:
<script>
import { mapState, mapGetters, mapActions } from "vuex";
export default {
methods: {
handleClickChangeAge(newAge) {
this.$store.commit('mc/changeAge',{
age: newAge
})
this.$store.commit({
type: 'mc/changeAge',
age: newAge
})
}
}
};
</script>
通過 getters
獲取 moduleC
中的 filterList()
,一種是返回值直接通過 this.$store
去寫,一種是語法糖的寫法
<script>
import { mapState, mapGetters, mapActions } from "vuex";
export default {
computed: {
list () {
return this.$store.getters['mc/filterList']
},
// ...mapGetters({
// list: 'mc/filterList'
// })
}
};
</script>
當然模塊里面再嵌套模塊也可以,路徑要不要多走一層主要看你的 namespaced: true
有沒有聲明,這里貼一下官方的代碼:
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
。換言之,你在使用模塊內容(module assets
)時不需要在同一模塊內額外添加空間名前綴。更改 namespaced
屬性后不需要修改模塊內的代碼。
-
在帶命名空間的模塊內訪問全局內容
如果你希望使用全局 state
和 getter
,rootState
和 rootGetters
會作為第三和第四參數傳入 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) { ... }
}
}
}
-
在帶命名空間的模塊注冊全局 action
若需要在帶命名空間的模塊注冊全局 action
,你可添加 root: true
,并將這個 action
的定義放在函數 handler
中。例如:
{
actions: {
someOtherAction ({dispatch}) {
dispatch('someAction') // 通過dispatch直接調用不需要加上命名空間
}
},
modules: {
foo: {
namespaced: true,
actions: {
someAction: { // 全局的action
root: true,
handler (namespacedContext, payload) { ... } // -> 'someAction'
}
}
}
}
}
-
在模塊里面使用輔助函數mapState、mapGetters、mapMutations和mapActions
由于存在命名空間,在組件里面采用上面的寫法會出現問題,這里要想使用輔助函數來映射模塊里面的東西需要指定空間名稱來告訴輔助函數應該去哪兒找這些。 這兒我以上面我的C模塊為例,首先對于 mapSatate
函數可以這樣玩,我在全局的 modules
里面聲明了 mc
,那我的空間名稱就是 mc
:
computed: {
...mapState('mc', ['name', 'desc']) // 這里模塊里面要使用輔助函數的話要多傳一個參數才行
}
然后在模版里面寫 name
,desc
即可,或者可以這樣:
computed: {
...mapState('mc', {
name(state) {
return state.name;
},
desc(state) {
return state.desc;
}
})
}
mapActions
、mapMutations
、mapGetter
都可以向上面一樣類似寫法,這里我們寫一個mapMutations
的栗子參考
<script>
import { mapState, mapGetters, mapActions, mapMutations } from "vuex";
export default {
methods: {
handleClickChangeAge(newAge) {
// 通過commit提交的寫法
// this.$store.commit({
// type: 'mc/changeAge',
// age: newAge
// })
// 使用語法糖的寫法
this.changeAge({
age: newAge
})
// 當...mapMutations中第二個參數使用對象寫法時,this后面接的函數名應該是該對象的鍵
this.editAge({
age: newAge
})
},
// 語法糖中第一個參數是對應的路徑,第二個參數為數組時的寫法
...mapMutations('mc', ['changeAge'])
// 第二個參數為對象時的寫法
...mapMutations('mc', {
editAge: 'changeAge' // 特意區分了鍵和值,值代表mutations中的函數,鍵代表了模板中this調用的函數
})
}
};
</script>
如果你確實不想在每個輔助函數里寫空間名稱,Vuex
也提供了其它辦法,使用createNamespacedHelpers
創建基于某個命名空間輔助函數,它返回一個對象,對象里有新的綁定在給定命名空間值上的組件綁定輔助函數:
import { createNamespacedHelpers } from 'vuex';
const { mapState, mapMutations } = createNamespacedHelpers('mc');
這樣你在寫輔助函數的時候就不需要單獨指定空間名稱了。 其它類似,就不再贅述了!其實 vuex
官網中對于 Module
這個板塊還有幾個知識點,只是對于了解基礎的話過多的深入可能還會影響自己的消化進度,如果當我們做一個項目龐大到需要建立很多個模塊,然后模塊中又進行嵌入,那么相信我們對 vuex
已經基本都了解了,到時候再去查閱相關的進階資料也很容易理解。
結語
vuex
的幾個核心概念的基本認識就都在這里了,本文也主要是參考了官網的文檔進行歸納總結。當然本篇相當于基礎入門篇,實際開發中使用 vuex
肯定遠遠比這個復雜,但是萬丈高樓平地起,希望對大家有所幫助,至于其他進階內容大家有興趣進官網瀏覽,也可查閱相關的資料進行學習。如果文章有理解不到位的地方,還請各位多批評指正!