學前準備
本文主要內(nèi)容來源于官網(wǎng)镶殷,為Vuex的基礎部分總結(jié)晦毙,部分案例也參考了互聯(lián)網(wǎng)上其他分享知識的大佬恋拷。本手記結(jié)合官網(wǎng)內(nèi)容也額外添加了自己的一些的理解准给,希望能給你帶來一些參考價值响巢,如果文章有理解不到位的地方描滔,還請各位多批評指正!以下是本文中可能用到的參考資料:
點擊進入vuex官方教程
點擊了解Vuex Action中的參數(shù)解構(gòu)為什么那么寫
為什么使用Vuex
Vuex的官方解答:Vuex 是一個專為 Vue.js 應用程序開發(fā)的狀態(tài)管理模式踪古。它采用集中式存儲管理應用的所有組件的狀態(tài)含长,并以相應的規(guī)則保證狀態(tài)以一種可預測的方式發(fā)生變化。
在vue開發(fā)的過程中伏穆,我們經(jīng)常遇到一個狀態(tài)可能會在多個組件之間使用拘泞,比如用戶信息、頭像枕扫、昵稱等陪腌,我們修改了這些信息,那么整個應用中凡是使用了該信息的部分都應該更新烟瞧。想讓vue
中所有組件都共享這些信息诗鸭,那么就需要將這些信息進行集中管理,這個時候我們就需要用到Vuex
参滴。
通過Vue CLI生成項目模板
日常開發(fā)中只泼,我們大多都是用Vue CLI
腳手架來生成一個vue項目,不太清楚腳手架怎么使用的可以移步Vue CLI官網(wǎng)自行查閱卵洗。在使用腳手架生成的項目時會讓我們選擇store
,選擇后會在頁面中給我們生成store
文件夾请唱,自帶初始化倉庫的index.js
,這就是最初的store
过蹂,里面結(jié)構(gòu)如下:
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
實例會被注入到所有的子組件里面贷币,而所有子組件中公用的數(shù)據(jù)信息和狀態(tài)就都存儲在State
里面。我們來看一個簡單的小栗子亏狰。
export default new Vuex.Store({
state: {
count: 102
}
})
vuex
中所有的狀態(tài)管理都會定義在 state
中役纹,我們在 state
中定義了 count
,那么我們就得到了一個集中管理的狀態(tài) count
暇唾,所有的組件都可以直接訪問到這個 count
促脉。
<template>
<div class="index">
{{count}}
</div>
</template>
<script>
export default {
computed: {
count () {
return this.$store.state.count
}
}
}
</script>
因為根實例中注冊 store
選項,該 store
實例會注入到根組件下的所有子組件中策州,且子組件能通過 this.$store
訪問到瘸味。通過計算屬性,我們就可以在模板里面使用模板語法來調(diào)用count
了够挂,當然我們也可以在模板中的直接寫入旁仿。有小伙伴會好奇為什么不能寫在data
里或其它地方嗎,state
寫在計算屬性中是因為模板文件需要響應倉庫中state
的變化孽糖,而state
變化之后如果寫在data
中就不能及時響應渲染到頁面中枯冈,而computed
身為計算屬性會監(jiān)聽函數(shù)下面值得變化然后進行重新渲染。
<template>
<div class="index">
{{this.$store.state.count}}
</div>
</template>
-
mapState
如果我們在store
中存儲了很多個狀態(tài)梭姓,而在當前模板頁面中又要讀取store
里面大量狀態(tài),那么我們在當前頁面采用computed
中來return store
里面的狀態(tài)這種寫法就會變得很冗余嫩码。Vuex
中針對state
給我們提供了它的語法糖mapState
誉尖,我們可以通過mapState()
方法直接獲取state中存儲的狀態(tài),如下栗子:
<template>
<div class="index">
{{count}}
{{total}}
</div>
</template>
<script>
// 使用該語法糖我們需提前引入
import { mapState } from 'vuex'
export default {
computed: {
total () {
return 10 + this.count
},
...mapState(['count']) // 如需引入多個狀態(tài)可寫成...mapState(['count', 'name', 'six'])
}
}
</script>
// 102
// 112
Getter
有時候我們需要從 store
中的 state
中派生出一些狀態(tài)铸题,例如如果state
中有一個數(shù)組铡恕,我們要將數(shù)組中所有大于10的數(shù)字挑選出來然后在給組件使用,栗子如下:
<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
中完成了代碼邏輯丢间,同時也暴露出了一些問題探熔,就是如果我們需要在多個組件中都完成這個數(shù)組的過濾,就需要在各個組件處都復制這段代碼或者將其抽取到一個共享函數(shù)然后在多處導入它烘挫,無論哪一種方式都不是很理想诀艰。這個時候我們就可以使用vuex
中的getter
。
Vuex
允許我們在 store
中定義“getter
”(可以認為是 store
的計算屬性)饮六。就像計算屬性一樣其垄,getter
的返回值會根據(jù)它的依賴被緩存起來,且只有當它的依賴值發(fā)生了改變才會被重新計算卤橄。
<!-- 使用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中的函數(shù)接收兩個參數(shù)(state, getters)
modiyArr(state) {
return state.list.filter(item => item > 10)
},
getLength(state, getters) {
return getters.modiyArr.length // 可以通過getters直接調(diào)用它的其它方法
}
}
})
// [20, 40, 50, 66, 77, 88]
上面的栗子基本都是通過屬性來訪問的绿满,使用getters
也可以支持通過方法來訪問,我們來看一個官網(wǎng)的小栗子
// 篩選state中的額todos窟扑,將todos數(shù)組中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
的響應式系統(tǒng)的一部分緩存其中的漏健。getter
在通過方法訪問時,每次都會去進行調(diào)用橘霎,而不會緩存結(jié)果蔫浆。
-
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
中的狀態(tài)進行修改克懊,我們是不能在組件里面直接修改的,因為組件里面直接修改倉庫是不會進行全局響應的七蜘,這就違背了我們使用倉庫的初衷谭溉。唯一的方法就是通過提交mutation
來進行修改,這樣倉庫中與之對應的狀態(tài)也會被修改進而影響全部組件的數(shù)據(jù)橡卤。Vuex
中的 mutation
非常類似于事件:每個 mutation
都有一個字符串的 事件類型 (type)
和 一個 回調(diào)函數(shù) (handler)
扮念。這個回調(diào)函數(shù)就是我們實際進行狀態(tài)更改的地方,并且它會接受 state
作為第一個參數(shù):
<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) {
// 變更狀態(tài)
state.num++
},
reduce (state) {
state.num--
}
}
})
上面栗子中碧库,我們在state
中定義了num
柜与,然后通過mutations
添加add
方法和reduce
方法去對num
進行修改,在模板文件中我們定義了click
事件來改變num
的值嵌灰,模板文件中的事件去響應store
中的mutations
主要是通過commit()
來實現(xiàn)的弄匕。
-
提交載荷(Payload)
你可以向store.commit
傳入額外的參數(shù),即 mutation 的 載荷(payload)
沽瞭,其實換種方式理解可能更容易迁匠,就是模板文件中的commit
可以添加額外的參數(shù),mutations
中接收的時候接收一個形參payload
驹溃,就代表模板文件中你傳進來的參數(shù)城丧。vuex
官網(wǎng)更建議我們payload
應該是一個對象,這樣可以包含多個字段并且記錄的 mutation
會更易讀豌鹤,我們看下面的小栗子:
export default {
methods: {
add() {
//this.$store.commit('add', 100)直接傳遞額外參數(shù)亡哄,不建議,更建議下一種對象的寫法
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--
}
}
})
也可以將所有參數(shù)寫到一個對象里面布疙,那么type
就對應mutations
中要執(zhí)行的函數(shù)名
export default {
methods: {
add() {
this.$store.commit({
type: 'add',
addNum: 100
})
}
}
}
-
Mutation 需遵守 Vue 的響應規(guī)則
這個通俗點說就是你在開發(fā)過程中需要向state
里面添加額外數(shù)據(jù)時蚊惯,需要遵循響應準則。官方文檔說既然 Vuex
的 store
中的狀態(tài)是響應式的灵临,那么當我們變更狀態(tài)時拣挪,監(jiān)視狀態(tài)的 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方法執(zhí)行之后頁面會立即響應這個新的狀態(tài)
<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狀態(tài),初始值為1000
// state= {...state, newNum: 2000} 這個方法不管用了舔痪,用下面的replaceState()方法
this.replaceState({...state, newNum: 2000})
}
}
-
Mutation 必須是同步函數(shù)
下面這種寫法必須避免(直接官方例子加持):
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
我們在模板文件中通過事件去操作mutations
時寓调,如果mutations
中為異步函數(shù),那么當 mutation
觸發(fā)的時候锄码,回調(diào)函數(shù)還沒有被調(diào)用夺英,因為我們不知道什么時候回調(diào)函數(shù)實際上被調(diào)用——實質(zhì)上任何在回調(diào)函數(shù)中進行的狀態(tài)的改變都是不可追蹤的。
-
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
重窟,而不是直接變更狀態(tài)载萌。 -
Action
可以包含任意異步操作。前面說過mutation
只能包含同步事務巡扇,所以在處理異步事務就需要Action
扭仁,通過Action
控制了異步這一過程,之后再去調(diào)用mutation
里面的方法來改變狀態(tài)厅翔。
先看官方的一個小栗子來認識它的基礎語法:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Action
函數(shù)接受一個與 store
實例具有相同方法和屬性的 context
對象乖坠,因此你可以調(diào)用 context.commit
提交一個 mutation
,或者通過 context.state
和 context.getters
來獲取 state
和 getters
刀闷。那為什么這里的參數(shù)不能直接是store
本身呢熊泵,這就和Modules
有關(guān)了,了解Modules
之后就會發(fā)現(xiàn)store
總倉庫下面可能會有很多個不同的模塊倉庫涩赢,而每一個不同的模塊倉庫都有自己的Action
戈次、state
轩勘、mutation
筒扒、getter
,如果使用store
那么store.state
拿到的就不會是當前模塊的state
绊寻,而context
可以理解為當前模塊的store
花墩,這樣就不會引起沖突。
實踐中澄步,我們會經(jīng)常用到 ES2015 的 參數(shù)解構(gòu) 來簡化代碼(特別是我們需要調(diào)用 commit 很多次的時候):
actions: {
increment ({ commit }) { // 這里就是將context展開式寫法 { commit } = context.commit
commit('increment')
}
}
-
分發(fā) Action
Action
通過 store.dispatch
方法觸發(fā):
store.dispatch('increment')
乍一眼看上去感覺多此一舉冰蘑,我們直接分發(fā) mutation
豈不更方便?實際上并非如此村缸,還記得 mutation
必須同步執(zhí)行這個限制么祠肥?Action
就不受約束!我們可以在 Action
內(nèi)部執(zhí)行異步操作梯皿。
上面這一塊內(nèi)容基本來源于官網(wǎng)仇箱,感覺官網(wǎng)對于 Action
函數(shù)的傳參县恕,分發(fā)等基本用法都說的比較詳細,在這里我們只要記住一點Action
用來執(zhí)行異步操作倉庫狀態(tài)的情況剂桥。我們還是來做個小栗子更直觀了解吧
<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
進行提交來更新數(shù)據(jù)美尸。至于分發(fā)Action
是我們在模板中通過 this.$store.dispatch('delayAdd')
來執(zhí)行,這樣我們就完成了一個基礎的Action
閉環(huán)demo
斟薇。
-
Actions 支持同樣的載荷方式和對象方式進行分發(fā):
// 以載荷形式分發(fā)
store.dispatch('delayAdd', {
addNum: 100
})
actions: {
delayAdd (context, payload) {
setTimeout(() => {
context.commit('add', payload.addNum)
}, 1000)
}
}
// 以對象形式分發(fā)
store.dispatch({
type: 'delayAdd',
addNum: 100
})
這里copy一個官方購物車示例师坎,涉及到調(diào)用異步 API 和分發(fā)多重 mutation
的栗子來給大家看看,主要是過一遍有個印象
actions: {
checkout ({ commit, state }, products) {
// 把當前購物車的物品備份起來
const savedCartItems = [...state.cart.added]
// 發(fā)出結(jié)賬請求奔垦,然后樂觀地清空購物車
commit(types.CHECKOUT_REQUEST)
// 購物 API 接受一個成功回調(diào)和一個失敗回調(diào)
shop.buyProducts(
products,
// 成功操作
() => commit(types.CHECKOUT_SUCCESS),
// 失敗操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
注意我們正在進行一系列的異步操作屹耐,并且通過提交 mutation
來記錄 action
產(chǎn)生的副作用(即狀態(tài)變更)。
-
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結(jié)合Promise
Action
通常是異步的犯眠,那么如何知道 Action
什么時候結(jié)束呢按灶?我們這里假設一種情況,模板上有一個數(shù)據(jù)筐咧,同時有這個數(shù)據(jù)當前的狀態(tài)說明鸯旁,我們?nèi)绻ㄟ^Action
去操作這個數(shù)據(jù),那么數(shù)據(jù)改變之后我們希望這個數(shù)據(jù)的當前狀態(tài)說明就馬上更新量蕊。舉個小栗子
<template>
<div class="index">
<div class="index-name">姓名:{{userinfo.name}}</div>
<div class="index-six">性別:{{userinfo.six}}</div>
<div class="number-hint">狀態(tài):{{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) // 執(zhí)行完畢铺罢,可以開始改變狀態(tài)啦
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: {
// 這里直接用參數(shù)解構(gòu)的寫法
changeInfo ({commit, state}, payload) { // 接收context(解構(gòu)寫法)和載荷payload
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('changeInfo', {
name: payload.name,
six: payload.six
})
resolve("執(zhí)行完畢,可以開始改變狀態(tài)啦") // 成功之后即可直接在then的回調(diào)中接收
}, payload.time * 1000)
})
}
}
})
上面的代碼基本就是先展示state
中存好的userinfo
信息残炮,點擊按鈕之后會有一個time
的倒計時來告訴你幾秒之后改變數(shù)據(jù)韭赘,改變之后再將文字改成"成功改變啦"。
其實上面這段代碼是有小瑕疵的势就,因為我們在分發(fā)action
的changeInfo
函數(shù)中給的是一個定時器泉瞻,沒有任何延遲信息,所以5s之后返回來的參數(shù)和我們在changeButton()
中寫好的改變button
狀態(tài)的文字能正好對應上苞冯。而日常開發(fā)中通常這里會寫請求函數(shù)到后端然后等待返回值袖牙,這個過程如果耽誤1s
那么們changeButton()
中就會出現(xiàn)問題,它會優(yōu)先執(zhí)行this.buttonText = '成功改變啦'
舅锄。
而實際因為請求有時間延遲鞭达,可能多出1s
就會出現(xiàn)文字改變而上面的信息并未更新。這里特意留給我們自己思考,如何完善這個小bug
畴蹭,其實答案很簡單烘贴,就是在then()
的回調(diào)中去處理這段邏輯,具體的實現(xiàn)可以自行去了解哦撮胧,對Promise
不太了解的可以點擊了解Promise桨踪。
-
組合Action
利用Promise來實現(xiàn)組和Action
我們先直接上官網(wǎng)的demo小栗子,通過栗子看更明白
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
其實上面的這個小栗子在我們上一塊 Action
結(jié)合Promise
中就說的很明白了芹啥,它就是想告訴我們通過Promise.resolve()
之后我們就知道這個時候actionA
就執(zhí)行完畢了锻离,我們就可以通過鏈式調(diào)用的then()
方法來走下一個流程啦∧够常看官方給的actionB
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
vue
模板中調(diào)用
store.dispatch('actionA').then((res) => {
// ... 這里執(zhí)行actionA中異步結(jié)束之后的程序嗤无,可以接受res為actionA中resolve("將返回結(jié)果返回出去")
})
利用 async / await來實現(xiàn)組和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 在不同模塊中可以觸發(fā)多個 action 函數(shù)布持。在這種情況下,只有當所有觸發(fā)函數(shù)完成后钓账,返回的 Promise 才會執(zhí)行碴犬。
Module
由于使用單一狀態(tài)樹,應用的所有狀態(tài)會集中到一個比較大的對象梆暮。當應用變得非常復雜時服协,store
對象就有可能變得相當臃腫。
為了解決以上問題啦粹,Vuex
允許我們將 store
分割成模塊(module
)偿荷。每個模塊擁有自己的state
、mutation
唠椭、action
跳纳、getter
、甚至是嵌套子模塊——從上至下進行同樣方式的分割贪嫂,我們先來做個簡單的栗子寺庄,這里我們就不直接在index.js
里面新建module
了,因為實際開發(fā)中我們也是將模塊單獨拎出來撩荣,這樣更有利于代碼維護铣揉。如下栗子:
// 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
都是掛載在根節(jié)點的state
下面。所以我們在組件中直接訪問模塊中的state
需要先去訪問根節(jié)點的state
然后在加上模塊名及對應的參數(shù)敌厘。
-
模塊的局部狀態(tài)
對于模塊內(nèi)部的 mutation
和 getter
台猴,接收的第一個參數(shù)是模塊的局部狀態(tài)對象。直接官方栗子加持:
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// 這里的 `state` 對象是模塊的局部狀態(tài)
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
同樣,對于模塊內(nèi)部的 action
饱狂,局部狀態(tài)通過 context.state
暴露出來曹步,根節(jié)點狀態(tài)則為 context.rootState
:
const moduleA = {
// ...
actions: {
// 這里用的解構(gòu)寫法,實際上是context.state, context.commit, context.rootState
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
對于模塊內(nèi)部的 getter
休讳,根節(jié)點狀態(tài)會作為第三個參數(shù)暴露出來:
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
看上面的代碼會發(fā)現(xiàn)我們通過 commit
提交模塊B
里面的內(nèi)容并沒有使用 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
來操作筹麸,而不需要再加上它所在的模塊名來進行調(diào)用,這里我們來看看官方是這樣說的:
默認情況下雏婶,模塊內(nèi)部的
action
物赶、mutation
和getter
是注冊在全局命名空間的——這樣使得多個模塊能夠?qū)ν?mutation
或action
作出響應。
-
命名空間
如果希望你的模塊具有更高的封裝度和復用性留晚,你可以通過添加 namespaced: true
的方式使其成為帶命名空間的模塊酵紫。當模塊被注冊后,它的所有 getter
错维、action
及 mutation
都會自動根據(jù)模塊注冊的路徑調(diào)整命名奖地。直接來看小栗子:
// 新建模塊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,
// 模塊內(nèi)容(module assets)
state: () => ({ ... }), // 模塊內(nèi)的狀態(tài)已經(jīng)是嵌套的了泽示,使用 `namespaced` 屬性不會對其產(chǎn)生影響
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
蜜氨。換言之械筛,你在使用模塊內(nèi)容(module assets
)時不需要在同一模塊內(nèi)額外添加空間名前綴。更改 namespaced
屬性后不需要修改模塊內(nèi)的代碼飒炎。
-
在帶命名空間的模塊內(nèi)訪問全局內(nèi)容
如果你希望使用全局 state
和 getter
埋哟,rootState
和 rootGetters
會作為第三和第四參數(shù)傳入 getter
,也會通過 context
對象的屬性傳入 action
郎汪。
若需要在全局命名空間內(nèi)分發(fā) action
或提交 mutation
赤赊,將 { root: true }
作為第三參數(shù)傳給 dispatch
或 commit
即可。這里直接官方栗子加持
modules: {
foo: {
namespaced: true,
getters: {
// 在這個模塊的 getter 中煞赢,`getters` 被局部化了
// 你可以使用 getter 的第四個參數(shù)來調(diào)用 `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
的定義放在函數(shù) handler
中吹截。例如:
{
actions: {
someOtherAction ({dispatch}) {
dispatch('someAction') // 通過dispatch直接調(diào)用不需要加上命名空間
}
},
modules: {
foo: {
namespaced: true,
actions: {
someAction: { // 全局的action
root: true,
handler (namespacedContext, payload) { ... } // -> 'someAction'
}
}
}
}
}
-
在模塊里面使用輔助函數(shù)mapState瘦陈、mapGetters、mapMutations和mapActions
由于存在命名空間波俄,在組件里面采用上面的寫法會出現(xiàn)問題晨逝,這里要想使用輔助函數(shù)來映射模塊里面的東西需要指定空間名稱來告訴輔助函數(shù)應該去哪兒找這些。 這兒我以上面我的C模塊為例懦铺,首先對于 mapSatate
函數(shù)可以這樣玩捉貌,我在全局的 modules
里面聲明了 mc
,那我的空間名稱就是 mc
:
computed: {
...mapState('mc', ['name', 'desc']) // 這里模塊里面要使用輔助函數(shù)的話要多傳一個參數(shù)才行
}
然后在模版里面寫 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中第二個參數(shù)使用對象寫法時,this后面接的函數(shù)名應該是該對象的鍵
this.editAge({
age: newAge
})
},
// 語法糖中第一個參數(shù)是對應的路徑叔汁,第二個參數(shù)為數(shù)組時的寫法
...mapMutations('mc', ['changeAge'])
// 第二個參數(shù)為對象時的寫法
...mapMutations('mc', {
editAge: 'changeAge' // 特意區(qū)分了鍵和值统求,值代表mutations中的函數(shù),鍵代表了模板中this調(diào)用的函數(shù)
})
}
};
</script>
如果你確實不想在每個輔助函數(shù)里寫空間名稱据块,Vuex
也提供了其它辦法码邻,使用createNamespacedHelpers
創(chuàng)建基于某個命名空間輔助函數(shù),它返回一個對象另假,對象里有新的綁定在給定命名空間值上的組件綁定輔助函數(shù):
import { createNamespacedHelpers } from 'vuex';
const { mapState, mapMutations } = createNamespacedHelpers('mc');
這樣你在寫輔助函數(shù)的時候就不需要單獨指定空間名稱了像屋。 其它類似,就不再贅述了边篮!其實 vuex
官網(wǎng)中對于 Module
這個板塊還有幾個知識點己莺,只是對于了解基礎的話過多的深入可能還會影響自己的消化進度,如果當我們做一個項目龐大到需要建立很多個模塊戈轿,然后模塊中又進行嵌入凌受,那么相信我們對 vuex
已經(jīng)基本都了解了,到時候再去查閱相關(guān)的進階資料也很容易理解思杯。
結(jié)語
vuex
的幾個核心概念的基本認識就都在這里了胜蛉,本文也主要是參考了官網(wǎng)的文檔進行歸納總結(jié)。當然本篇相當于基礎入門篇色乾,實際開發(fā)中使用 vuex
肯定遠遠比這個復雜誊册,但是萬丈高樓平地起,希望對大家有所幫助暖璧,至于其他進階內(nèi)容大家有興趣進官網(wǎng)瀏覽案怯,也可查閱相關(guān)的資料進行學習。如果文章有理解不到位的地方漆撞,還請各位多批評指正殴泰!