一面哥、前言
當(dāng)我們的應(yīng)用遇到多個組件共享狀態(tài)時分唾,會需要多個組件依賴于同一狀態(tài)抑或是來自不同視圖的行為需要變更同一狀態(tài)校套。以前的解決辦法:
a.將數(shù)據(jù)以及操作數(shù)據(jù)的行為都定義在父組件;
b.將數(shù)據(jù)以及操作數(shù)據(jù)的行為傳遞給需要的各個子組件(有可能需要多級傳遞)
傳參的方法對于多層嵌套的組件將會非常繁瑣纯续,并且對于兄弟組件間的狀態(tài)傳遞無能為力。在搭建下面頁面時羽莺,你可能會對 vue 組件之間的通信感到崩潰 实昨,特別是非父子組件之間通信。此時就應(yīng)該使用vuex盐固,輕松可以搞定組件間通信問題荒给。
二丈挟、什么是Vuex
Vuex 是一個專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài)志电,并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化曙咽。這里的關(guān)鍵在于集中式存儲管理。簡單來說,對 vue 應(yīng)用中多個組件的共享狀態(tài)進(jìn)行集中式的管理(讀/寫)挑辆。
三例朱、Vuex的原理是什么
1.簡要介紹Vuex原理
Vuex實現(xiàn)了一個單向數(shù)據(jù)流,在全局擁有一個State存放數(shù)據(jù)鱼蝉,當(dāng)組件要更改State中的數(shù)據(jù)時洒嗤,必須通過Mutation進(jìn)行,Mutation同時提供了訂閱者模式供外部插件調(diào)用獲取State數(shù)據(jù)的更新蚀乔。而當(dāng)所有異步操作(常見于調(diào)用后端接口異步獲取更新數(shù)據(jù))或批量的同步操作需要走Action,但Action也是無法直接修改State的菲茬,還是需要通過Mutation來修改State的數(shù)據(jù)吉挣。最后,根據(jù)State的變化婉弹,渲染到視圖上睬魂。
2.簡要介紹各模塊在流程中的主要功能:
- Vue Components:Vue組件。HTML頁面上镀赌,負(fù)責(zé)接收用戶操作等交互行為氯哮,執(zhí)行dispatch方法觸發(fā)對應(yīng)action進(jìn)行回應(yīng)。
- dispatch:操作行為觸發(fā)方法商佛,是唯一能執(zhí)行action的方法喉钢。
- actions:操作行為處理模塊,由組件中的
$store.dispatch('action 名稱', data1)
來觸發(fā)。然后由commit()來觸發(fā)mutation的調(diào)用 , 間接更新 state良姆。負(fù)責(zé)處理Vue Components接收到的所有交互行為肠虽。包含同步/異步操作,支持多個同名方法玛追,按照注冊的順序依次觸發(fā)税课。向后臺API請求的操作就在這個模塊中進(jìn)行,包括觸發(fā)其他action以及提交mutation的操作痊剖。該模塊提供了Promise的封裝韩玩,以支持action的鏈?zhǔn)接|發(fā)。 - commit:狀態(tài)改變提交操作方法陆馁。對mutation進(jìn)行提交找颓,是唯一能執(zhí)行mutation的方法。
- mutations:狀態(tài)改變操作方法叮贩,由actions中的
commit('mutation 名稱')
來觸發(fā)叮雳。是Vuex修改state的唯一推薦方法想暗。該方法只能進(jìn)行同步操作,且方法名只能全局唯一帘不。操作之中會有一些hook暴露出來说莫,以進(jìn)行state的監(jiān)控等。 - state:頁面狀態(tài)管理容器對象寞焙。集中存儲Vue components中data對象的零散數(shù)據(jù)储狭,全局唯一,以進(jìn)行統(tǒng)一的狀態(tài)管理捣郊。頁面顯示所需的數(shù)據(jù)從該對象中進(jìn)行讀取辽狈,利用Vue的細(xì)粒度數(shù)據(jù)響應(yīng)機制來進(jìn)行高效的狀態(tài)更新。
- getters:state對象讀取方法呛牲。圖中沒有單獨列出該模塊刮萌,應(yīng)該被包含在了render中,Vue Components通過該方法讀取全局state對象娘扩。
四着茸、什么時候使用Vuex
雖然 Vuex 可以幫助我們管理共享狀態(tài),但也附帶了更多的概念和框架琐旁。這需要對短期和長期效益進(jìn)行權(quán)衡涮阔。
如果您的應(yīng)用夠簡單,您最好不要使用 Vuex,因為使用 Vuex 可能是繁瑣冗余的灰殴。一個簡單的 global event bus 就足夠您所需了敬特。但是,如果您需要構(gòu)建一個中大型單頁應(yīng)用牺陶,您很可能會考慮如何更好地在組件外部管理狀態(tài)伟阔,Vuex 將會成為自然而然的選擇。
五掰伸、Vuex安裝(限定開發(fā)環(huán)境為 vue-cli)
首先要安裝vue-cli腳手架减俏,對于大陸用戶,建議將npm的注冊表源設(shè)置為國內(nèi)的鏡像(淘寶鏡像)碱工,可以大幅提升安裝速度娃承。
npm config set registry https://[registry.npm.taobao.org](http://registry.npm.taobao.org/)
npm config get registry//配置后可通過下面方式來驗證是否成功
npm install -g cnpm --registry=[https://registry](https://registry/).npm.taobao.org
//cnpm安裝腳手架
cnpm install -g vue-cli
vue init webpack my-vue
cd my-vue
cnpm install
cnpm run dev
腳手架安裝好后,再安裝vuex
cnpm install vuex --save
六怕篷、如何使用Vuex
1.如何通過Vue來實現(xiàn)如下效果历筝?
這個小demo很容易用vue實現(xiàn),核心代碼如下:
<div class="hello">
<p>click {{count}} times,count is {{evenOrOdd}}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
......
export default {
name: "HelloWorld",
data() {
return {
count: 0
};
},
computed: {
evenOrOdd() {
return this.count % 2 === 0 ? "偶數(shù)" : "奇數(shù)";
}
},
methods: {
increment() {
this.count = this.count + 1;
},
decrement() {
this.count = this.count - 1;
},
// 只有是奇數(shù)才加1
incrementIfOdd() {
if (this.count % 2 === 1) {
this.count = this.count + 1;
}
},
// 過兩秒才加1
incrementAsync() {
setInterval(() => {
this.count = this.count + 1;
}, 2000);
}
}
}
2.如何通過Vuex來改造上面代碼廊谓?
①創(chuàng)建一個store.js文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {// 包含了多個直接更新state函數(shù)的對象
INCREMENT(state) {
state.count = state.count + 1;
},
DECREMENT(state) {
state.count = state.count - 1;
}
},
getters: { // 當(dāng)讀取屬性值時自動調(diào)用并返回屬性值
evenOrOdd(state) {
return state.count % 2 === 0 ? "偶數(shù)" : "奇數(shù)";
}
},
actions: { // 包含了多個對應(yīng)事件回調(diào)函數(shù)的對象
incrementIfOdd({ commit, state }) { // 帶條件的action
if (state.count % 2 === 1) {
commit('INCREMENT')
}
},
incrementAsync({ commit }) { //異步的action
setInterval(() => {
commit('INCREMENT')
}, 2000);
}
}
})
export default store //用export default 封裝代碼梳猪,讓外部可以引用
②在main.js文件中引入store.js文件
import store from './store'
new Vue({
el: '#app',
router,
store,//注冊上vuex的store: 所有組件對象都多一個屬性$store
components: { App },
template: '<App/>'
})
③新建一個模板HelloWorld.vue
<template>
<div class="hello">
<p>click {{count}} times,count is {{evenOrOdd}}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
computed: {
count() {
return this.$store.state.count;
},
evenOrOdd() {
return this.$store.getters.evenOrOdd;
}
},
methods: {
increment() {
this.$store.commit("INCREMENT");
},
decrement() {
this.$store.commit("DECREMENT");
},
// 只有是奇數(shù)才加1
incrementIfOdd() {
this.$store.dispatch("incrementIfOdd"); //觸發(fā)store中對應(yīng)的action調(diào)用
},
// 過兩秒才加1
incrementAsync() {
this.$store.dispatch("incrementAsync");
}
}
};
</script>
由于 store 中的狀態(tài)是響應(yīng)式的,當(dāng) Vue 組件從 store 中讀取狀態(tài)的時候,若 store 中的狀態(tài)發(fā)生變化春弥,那么相應(yīng)的組件也會相應(yīng)地得到高效更新呛哟。在組件中調(diào)用 store 中的狀態(tài)簡單到僅需要在計算屬性中返回即可。改變store 中的狀態(tài)的唯一途徑就是顯式地提交 (commit) mutations匿沛。
3.如何通mapState等輔助函數(shù)優(yōu)化上面代碼扫责?
import { mapActions, mapGetters, mapState, mapMutations } from "vuex";
...
computed: {
...mapState(["count"]),
...mapGetters(["evenOrOdd"])
}
methods: {
...mapActions(["incrementIfOdd", "incrementAsync"]),
...mapMutations(["increment", "decrement"])
}
有點必須要注意:HelloWorld.vue文件中increment函數(shù)名稱要跟store.js文件mutations中一致,才可以寫成 ...mapMutations(["increment", "decrement"])逃呼,同樣的道理鳖孤,incrementIfOdd和incrementAsync也要和store.js文件actions保持一致。
七抡笼、使用Vuex的注意點
1.如何在Mutations里傳遞參數(shù)
先store.js文件里給add方法加上一個參數(shù)n
mutations: {
INCREMENT(state,n) {
state.count+=n;
},
DECREMENT(state){
state.count--;
}
}
然后在HelloWorld.vue里修改按鈕的commit( )方法傳遞的參數(shù)
increment() {
return this.$store.commit("INCREMENT",2);
},
decrement() {
return this.$store.commit("DECREMENT");
}
2.如何理解getters
getters從表面是獲得的意思苏揣,可以把他看作在獲取數(shù)據(jù)之前進(jìn)行的一種再編輯,相當(dāng)于對數(shù)據(jù)的一個過濾和加工。getters就像計算屬性一樣推姻,getter 的返回值會根據(jù)它的依賴被緩存起來平匈,且只有當(dāng)它的依賴值發(fā)生了改變才會被重新計算。
例如:要對store.js文件中的count進(jìn)行操作藏古,在它輸出前增炭,給它加上100十拣。
首先要在store.js里Vuex.Store()里引入getters
getters:{
count:state=>state.count+=100
}
然后在HelloWorld.vue中對computed進(jìn)行配置,在vue 的構(gòu)造器里邊只能有一個computed屬性藐石,如果你寫多個容握,只有最后一個computed屬性可用,所以要用展開運算符”…”對上節(jié)寫的computed屬性進(jìn)行一個改造赌结。
computed: {
...mapGetters(["count"])
}
3.actions和mutations區(qū)別
actions和上面的Mutations功能基本一樣,不同點是,actions是異步的改變state狀態(tài)蔫敲,而Mutations是同步改變狀態(tài)。
同步的意義在于這樣每一個 mutation 執(zhí)行完成后都可以對應(yīng)到一個新的狀態(tài)(和 reducer 一樣)炭玫,這樣 devtools 就可以打個 snapshot 存下來奈嘿,然后就可以隨便 time-travel 了。如果你開著 devtool 調(diào)用一個異步的 action吞加,你可以清楚地看到它所調(diào)用的 mutation 是何時被記錄下來的裙犹,并且可以立刻查看它們對應(yīng)的狀態(tài)----尤雨溪
ps:如果想訪問源代碼,請猛戳git地址
如果覺得文章對你有些許幫助衔憨,歡迎在我的GitHub博客點贊和關(guān)注叶圃,感激不盡!