第一步,了解Vuex
??想象一個場景
如果你的項目里有很多頁面(組件/視圖)仑乌,頁面之間存在多級的嵌套關系百拓,此時琴锭,這些頁面假如都需要共享一個狀態(tài)的時候,此時就會產(chǎn)生以下兩個問題:
多個視圖依賴同一個狀態(tài)
來自不同視圖的行為需要變更同一個狀態(tài)
?? 動動你的小腦袋你就會想到解決以上方法的方案:
對于第一個問題耐版,假如是多級嵌套關系祠够,你可以使用父子組件傳參進行解決,雖有些麻煩粪牲,但好在可以解決古瓤;對于兄弟組件或者關系更復雜組件之間,就很難辦了腺阳,雖然可以通過各種各樣的辦法解決落君,可實在很不優(yōu)雅,而且等項目做大了亭引,代碼就會變成屎山绎速,實在令人心煩。
對于第二個問題焙蚓,你可以通過父子組件直接引用纹冤,或者通過事件來變更或者同步狀態(tài)的多份拷貝,這種模式很脆弱购公,往往使得代碼難以維護萌京,而且同樣會讓代碼變成屎山。
?? 此時宏浩,既然思考到了這里知残,如果換一種思路呢:
把各個組件都需要依賴的同一個狀態(tài)抽取出來,在全局使用單例模式進行管理比庄。
在這種模式下求妹,任何組件都可以直接訪問到這個狀態(tài),或者當狀態(tài)發(fā)生改變時佳窑,所有的組件都獲得更新制恍。
?? 這時候,Vuex誕生了神凑!
這就是 Vuex 背后的基本思想吧趣,借鑒了 Flux、Redux耙厚。與其他模式不同的是强挫,Vuex 是專門為 Vue 設計的狀態(tài)管理庫,以利用 Vue.js 的細粒度數(shù)據(jù)響應機制來進行高效的狀態(tài)更新薛躬。
第二步俯渤,安裝
進入項目,在命令行中輸入安裝指令型宝,回車
npm install vuex --save
然后配置 vuex八匠,使其工作起來:在src路徑下創(chuàng)建store文件夾絮爷,然后創(chuàng)建index.js文件,文件內(nèi)容如下:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
// 定義一個name梨树,以供全局使用
name: '張三',
// 定義一個number坑夯,以供全局使用
number: 0,
// 定義一個list,以供全局使用
list: [
{ id: 1, name: '111' },
{ id: 2, name: '222' },
{ id: 3, name: '333' },
]
},
});
export default store;
修改main.js:
import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store'; // 引入我們前面導出的store對象
Vue.config.productionTip = false;
new Vue({
el: '#app',
router,
store, // 把store對象添加到vue實例上
components: { App },
template: '<App/>'
});
最后修改App.vue:
<template>
<div></div>
</template>
<script>
export default {
mounted() {
// 使用this.$store.state.XXX可以直接訪問到倉庫中的狀態(tài)
console.log(this.$store.state.name);
}
}
</script>
此時抡四,啟動項目npm run dev
柜蜈,即可在控制臺輸出剛才我們定義在store中的name的值。
- ?? 官方建議1:
官方建議我們以上操作this.$store.state.XXX最好放在計算屬性中指巡,當然淑履,我也建議你這么使用,這樣可以讓你的代碼看起來更優(yōu)雅一些藻雪,就像這樣:
<script>
import { mapState } from 'vuex'; // 從vuex中導入mapState
export default {
mounted() {
console.log(this.name);
},
computed: {
...mapState(['name']), // 經(jīng)過解構后秘噪,自動就添加到了計算屬性中,此時就可以直接像訪問計算屬性一樣訪問它
},
}
</script>
你甚至可以在解構的時候給它賦別名勉耀,取外號指煎,就像這樣:
...mapState({ aliasName: 'name' }), // 賦別名的話,這里接收對象便斥,而不是數(shù)組
?? 至此至壤,安裝vuex并且初始化的工作就結束了,此時你可以很輕易的在項目的任意地方訪問到倉庫里的狀態(tài)
第三步椭住,了解修飾器:Getter
當你看到這里的時候崇渗,證明你上一步已經(jīng)完美的創(chuàng)建好一個vue項目字逗,并且將vuex安裝了進去京郑!
好!接下來葫掉,我們介紹一個讀取操作的 “修飾利器” ---Getter
?? 設想一個場景些举,你已經(jīng)將store中的name展示到頁面上了,而且是很多頁面都展示了俭厚,此時產(chǎn)品經(jīng)理過來找事兒??:
產(chǎn)品經(jīng)理:所有的name前面都要加上“hello”户魏!
我:為什么?
產(chǎn)品經(jīng)理:我提需求還需要為什么嗎挪挤?
我:好叼丑,我加!
這時候扛门,你第一想到的是怎么加呢鸠信,emm...在每個頁面上,使用this.$store.state.name獲取到值之后论寨,進行遍歷星立,前面追加"hello"即可爽茴。
????♂? 錯!這樣很不好绰垂,原因如下:
第一室奏,假如你在A、B劲装、C三個頁面都用到了name胧沫,那么你要在這A、B酱畅、C三個頁面都修改一遍琳袄,多個頁面你就要加很多遍這個方法,造成代碼冗余纺酸,很不好窖逗;
第二,假如下次產(chǎn)品經(jīng)理讓你把 “hello” 改成 “fuck” 的時候餐蔬,你又得把三個頁面都改一遍碎紊,這時候你只能抽自己的臉了...
???? 吸取上面的教訓,你會有一個新的思路:我們可以直接在store中對name進行一些操作或者加工樊诺,從源頭解決問題仗考!那么具體應該怎么寫呢?這時候词爬,本次將要介紹的這個Getter利器閃亮登場秃嗜!
?? 怎么用呢?不廢話顿膨,show code锅锨!
首先,在store對象中增加getters屬性
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
name: '張三',
number: 0,
list: [
{ id: 1, name: '111' },
{ id: 2, name: '222' },
{ id: 3, name: '333' },
]
},
// 在store對象中增加getters屬性
getters: {
getMessage(state) { // 獲取修飾后的name恋沃,第一個參數(shù)state為必要參數(shù)必搞,必須寫在形參上
return `hello${state.name}`;
}
},
});
export default store;
在組件中使用:
export default {
mounted() {
// 注意不是$store.state了,而是$store.getters
console.log(this.$store.state.name);
console.log(this.$store.getters.getMessage);
}
}
然后查看控制臺:
沒有問題的
?? 官方建議:是不是每次都寫this.$store.getters.XXX讓你感到厭煩囊咏,你實在不想寫這個東西怎么辦恕洲,當然有解決方案,官方建議我們可以使用mapGetters去解構到計算屬性中梅割,就像使用mapState一樣霜第,就可以直接使用this調用了,就像下面這樣:
<script>
import { mapState, mapGetters } from 'vuex';
export default {
mounted() {
console.log(this.name);
console.log(this.getMessage);
},
computed: {
...mapState(['name']),
...mapGetters(['getMessage']),
},
}
</script>
此時可以得到和之前一樣的效果户辞。
當然泌类,和mapState一樣你也可以取別名,取外號咆课,就像下面這樣:
...mapGetters({ aliasName: 'getMessage' }), // 賦別名的話末誓,這里接收對象扯俱,而不是數(shù)組
?? OK,當你看到這里喇澡,你已經(jīng)成功的把Getter用起來了迅栅,你也能明白在什么時候應該用到getters,你可以通過計算屬性訪問(緩存)晴玖,也可以通過方法訪問(不緩存)读存,你甚至可以在getters的方法里面再調用getters方法,當然你也實現(xiàn)了像state那樣呕屎,使用mapGetters解構到計算屬性中让簿,這樣你就可以很方便的使用getters啦!
?? 讀取值的操作我們有 “原生讀(state)” 和 “修飾讀(getters)”秀睛,接下來就要介紹怎么修改值了尔当!
第四步,了解如何修改值:Mutation
?? OK蹂安!首先恭喜你看到了這里椭迎,至此,我們已經(jīng)成功訪問到了store里面的值田盈,接下來我來介紹一下怎么修改state里面的值畜号。
說到修改值,有的同學就會想到這樣寫:
// 錯誤示范
this.$store.state.XXX = XXX;
?? 首先允瞧,這里我先明確的說明:這是錯誤的寫法简软!這是錯誤的寫法!這是錯誤的寫法述暂!
為什么上面是錯誤的寫法痹升?因為這個store倉庫比較奇怪,你可以隨便拿贸典,但是你不能隨便改视卢,我舉個例子:
?? 假如你打開微信朋友圈踱卵,看到你的好友發(fā)了動態(tài)廊驼,但是動態(tài)里有個錯別字,你要怎么辦呢惋砂?你可以幫他改掉嗎妒挎?當然不可以!我們只能通知他本人去修改西饵,因為是別人的朋友圈酝掩,你是無權操作的,只有他自己才能操作眷柔,同理期虾,在vuex中原朝,我們不能直接修改倉庫里的值,必須用vuex自帶的方法去修改镶苞,這個時候喳坠,Mutation閃亮登場了!
?? 把問題解釋清楚之后茂蚓,我們準備完成一個效果:我們先輸出state中的number的默認值0壕鹉,然后我們在vue組件里通過提交Mutations改變number的默認值0,改成我們想修改的值聋涨,然后再輸出出來晾浴,這樣就可以簡單練習怎么使用Mutations了。不說廢話牍白,上代碼脊凰。
修改store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
name: '張三',
number: 0,
},
mutations: { // 增加nutations屬性
setNumber(state) { // 增加一個mutations的方法,方法的作用是讓num從0變成5茂腥,state是必須默認參數(shù)
state.number = 5;
}
},
});
export default store;
修改App.vue
<script>
export default {
mounted() {
console.log(`舊值:${this.$store.state.number}`);
this.$store.commit('setNumber');
console.log(`新值:${this.$store.state.number}`);
},
}
</script>
- 運行項目笙各,查看控制臺:
?? 以上是簡單實現(xiàn)mutations的方法,是沒有傳參的础芍,如果我們想傳不固定的參數(shù)怎么辦杈抢?接下來教你解決
修改store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
name: '張三',
number: 0,
},
mutations: {
setNumber(state) {
state.number = 5;
},
setNumberIsWhat(state, number) { // 增加一個帶參數(shù)的mutations方法
state.number = number;
},
},
});
export default store;
修改App.vue
<script>
export default {
mounted() {
console.log(`舊值:${this.$store.state.number}`);
this.$store.commit('setNumberIsWhat', 666);
console.log(`新值:${this.$store.state.number}`);
},
}
</script>
- 運行項目,查看控制臺:
沒有問題仑性!
注意:上面的這種傳參的方式雖然可以達到目的惶楼,但是并不推薦,官方建議傳遞一個對象進去诊杆,這樣看起來更美觀歼捐,對象的名字你可以隨意命名,但我們一般命名為payload晨汹,代碼如下:
修改store/index.js
mutations: {
setNumber(state) {
state.number = 5;
},
setNumberIsWhat(state, payload) { // 增加一個帶參數(shù)的mutations方法豹储,并且**官方建議**payload為一個對象
state.number = payload.number;
},
},
修改App.vue
<script>
export default {
mounted() {
console.log(`舊值:${this.$store.state.number}`);
this.$store.commit('setNumberIsWhat', { number: 666 }); // 調用的時候也需要傳遞一個對象
console.log(`新值:${this.$store.state.number}`);
},
}
</script>
此時可以得到和之前一樣的效果,并且代碼更加美觀淘这!
?? 這里說一條重要原則:Mutations里面的函數(shù)必須是同步操作剥扣,不能包含異步操作!(別急铝穷,后面會講到異步)
?? 這里說一條重要原則:Mutations里面的函數(shù)必須是同步操作钠怯,不能包含異步操作!(別急曙聂,后面會講到異步)
?? 這里說一條重要原則:Mutations里面的函數(shù)必須是同步操作晦炊,不能包含異步操作!(別急,后面會講到異步)
好的断国,記住這個重要原則贤姆,我們再說一個小技巧:
?? 官方建議:就像最開始的mapState和mapGetters一樣,我們在組件中可以使用mapMutations以代替this.$store.commit('XXX')稳衬,是不是很方便呢庐氮?
<script>
import { mapMutations } from 'vuex';
export default {
mounted() {
this.setNumberIsWhat({ number: 999 });
},
methods: { // 注意,mapMutations是解構到methods里面的宋彼,而不是計算屬性了
...mapMutations(['setNumberIsWhat']),
},
}
</script>
此時可以得到和之前一樣的效果弄砍,并且代碼又美觀了一點扔傅!
當然你也可以給它叫別名蒜撮,取外號组题,就像這樣:
methods:{
...mapMutations({ setNumberIsAlias: 'setNumberIsWhat' }), // 賦別名的話先紫,這里接收對象舵匾,而不是數(shù)組
}
?? OK盐肃,關于Mutation的介紹大致就是這樣帚屉,另外你也掌握了在定義mutations方法的時候有無參數(shù)應該怎么寫渤愁;并且聽取了官方建議檐什,使用mapMutations解構到你的組件內(nèi)部的methods里碴卧,這樣你就可以很簡單的使用mutations方法啦!
?? 上面提到乃正,Mutations只能進行同步操作住册,所以,我們馬上開始下一節(jié)瓮具,看看使用Actions進行異步操作的時候應該注意什么荧飞!
第五步,了解異步操作:Actions
?? OK名党!本節(jié)我們來學習使用Actions叹阔,Actions存在的意義是假設你在修改state的時候有異步操作,vuex作者不希望你將異步操作放在Mutations中传睹,所以就給你設置了一個區(qū)域耳幢,讓你放異步操作,這就是Actions
?? 我們直接上一個代碼
修改store/index.js
const store = new Vuex.Store({
state: {
name: '張三',
number: 0,
},
mutations: {
setNumberIsWhat(state, payload) {
state.number = payload.number;
},
},
actions: { // 增加actions屬性
setNum(content) { // 增加setNum方法欧啤,默認第一個參數(shù)是content睛藻,其值是復制的一份store
return new Promise(resolve => { // 我們模擬一個異步操作,1秒后修改number為888
setTimeout(() => {
content.commit('setNumberIsWhat', { number: 888 });
resolve();
}, 1000);
});
}
}
});
修改App.vue
async mounted() {
console.log(`舊值:${this.$store.state.number}`);
await this.$store.dispatch('setNum');
console.log(`新值:${this.$store.state.number}`);
},
- 運行項目堂油,查看控制臺:
?? 看了例子修档,是不是明白了碧绞,action就是去提交mutation的府框,什么異步操作都在action中消化了,最后再去提交mutation的。
?? 當然迫靖,你可以模仿mutation進行傳參院峡,就像下面這樣:
- 修改store/index.js
actions: {
setNum(content, payload) { // 增加payload參數(shù)
return new Promise(resolve => {
setTimeout(() => {
content.commit('setNumberIsWhat', { number: payload.number });
resolve();
}, 1000);
});
},
}
修改App.vue
async mounted() {
console.log(`舊值:${this.$store.state.number}`);
await this.$store.dispatch('setNum', { number: 611 });
console.log(`新值:${this.$store.state.number}`);
},
- 運行項目,查看控制臺
沒有任何問題系宜!
- ?? 官方建議1:你如果不想一直使用this.$store.dispatch('XXX')這樣的寫法調用action照激,你可以采用mapActions的方式,把相關的actions解構到methods中盹牧,用this直接調用:
<script>
import { mapActions } from 'vuex';
export default {
methods: {
...mapActions(['setNum']), // 就像這樣俩垃,解構到methods中
},
async mounted() {
await this.setNum({ number: 123 }); // 直接這樣調用即可
},
}
</script>
當然,你也可以取別名汰寓,取外號口柳,就像下面這樣:
...mapActions({ setNumAlias: 'setNum' }), // 賦別名的話,這里接收對象有滑,而不是數(shù)組
?? 官方建議2:在store/index.js中的actions里面跃闹,方法的形參可以直接將commit解構出來,這樣可以方便后續(xù)操作:
actions: {
setNum({ commit }) { // 直接將content結構掉毛好,解構出commit望艺,下面就可以直接調用了
return new Promise(resolve => {
setTimeout(() => {
commit('XXXX'); // 直接調用
resolve();
}, 1000);
});
},
},
?? OK,看到這里肌访,你應該明白action在vuex的位置了吧找默,什么時候該用action,什么時候不用它吼驶,你肯定有了自己的判斷啡莉,最主要的判斷條件就是我要做的操作是不是異步,這也是action存在的本質旨剥。當然咧欣,你不要將action和mutation混為一談,action其實就是mutation的上一級轨帜,在action這里處理完異步的一些操作后魄咕,后面的修改state就交給mutation去做了。