1. vuex簡介
vuex是專門用來管理vue.js應用程式中狀态的一個插件。他的作用是将應用中的所有狀态都放在一起,集中式來管理。需要聲明的是,這裡所說的狀态指的是vue元件中data裡面的屬性。了解vue的同學應該是明白data是怎麼回事的吧,如果不懂的話,建議先學完vue的基礎知識再看vuex。
2. vuex的組成結構示意圖
vuex的特點是把資料單獨隔離,形成一棵樹狀圖。單獨隔離就意味着它有自己的生态系統。輸入和輸出,其中action作為資料的輸入,state作為資料的輸出。如下圖:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCM581dvRWYoNHLwEzX5xCMx8FesU2cfdGLwATMfRHLGZkRGZkRfJ3bs92YskmNhVTYykVNQJVMRhXVEF1X0hXZ0xiNx8VZ6l2cssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL1ADOyIWOzETMzYTMyUzNxYzXxMzMwcTM0IzLcBTMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL1M3Lc9CX6MHc0RHaiojIsJye.png)
隻能在mutaions裡修改state,actions不能直接修改state
mutations即變化,修改state的資料,而且隻能是同步的,不能存在異步的操作。如果需要異步怎麼辦呢?把異步操作放在actions裡,拿到資料再通過mutations同步處理。vuex做的其實是把職權明确了,責任細分了。是以它文檔裡也說,小系統可以不用。狀态資料少,沒有細分的必要。
通過這個圖,我們很容易就對vuex的組成部分,以及vuex與元件(components)之間的聯系一目了然。其實這張圖,有點迷惑人,暫時看不懂也沒關系,隻要有一個直覺的認識就行了。這張描述圖就不用了
,大家請往下看。
3. vuex 的核心概念
3.1 store
vuex 中最關鍵的是store對象,這是vuex的核心。可以說,vuex這個插件其實就是一個store對象,每個vue應用僅且僅有一個store對象。
3.1.1 建立store
const store = new Vuex.Store({...});
可見,store是Vuex.Store這個構造函數new出來的執行個體。在構造函數中可以傳一個對象參數。這個參數中可以包含5個對象:
- 1.state – 存放狀态
- 2.getters – state的計算屬性
- 3.mutations – 更改狀态的邏輯,同步操作
- 4.actions – 送出mutation,異步操作
- 5.mudules – 将store子產品化
關于store,需要先記住兩點:
- 1. store 中存儲的狀态是響應式的,當元件從store中讀取狀态時,如果store中的狀态發生了改變,那麼相應的元件也會得到更新;
- 2. 不能直接改變store中的狀态。改變store中的狀态的唯一途徑是送出(commit)mutations。這樣使得我們可以友善地跟蹤每一個狀态的變化。
3.1.2 一個完整的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,
// ...
}
});
3.2 state
state上存放的,說的簡單一些就是變量,也就是所謂的狀态。沒有使用 state 的時候,我們都是直接在 data 中進行初始化的,但是有了 state 之後,我們就把 data 上的資料轉移到 state 上去了。另外有些狀态是元件私有的狀态,稱為元件的局部狀态,我們不需要把這部分狀态放在store中去。
3.2.1 如何在元件中擷取vuex狀态
由于vuex的狀态是響應式的,是以從store中讀取狀态的的方法是在元件的計算屬性中傳回某個狀态。
import store from 'store';
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
// 擷取store中的狀态
return store.state.count;
}
}
}
這樣,元件中的狀态就與store中的狀态關聯起來了。每當store.state.count發生變化時,都會重新求取計算屬性,進而更新DOM。
然而,每個元件中都需要反複倒入store。可以将store注入到vue執行個體對象中去,這樣每一個子元件中都可以直接擷取store中的狀态,而不需要反複的倒入store了。
const app = new Vue({
el: '#app',
// 把 store 對象注入到了
store,
components: { Counter },
template: `
<div>
<counter></counter>
</div>
`
});
這樣可以在子元件中使用this.$store.state.count通路到state裡面的count這個狀态
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
// 擷取store中的狀态
return this.$store.state.count;
}
}
}
3.2.2 mapState
當一個元件擷取多種狀态的時候,則在計算屬性中要寫多個函數。為了友善,可以使用mapState輔助函數來幫我們生成計算屬性。
import { mapState } from 'vuex';
export default {
// ...
data (){
localState: 1
}
computed: mapState({
// 此處的state即為store裡面的state
count: state => state.count,
// 當計算屬性的名稱與state的狀态名稱一樣時,可以省寫
// 映射 this.count1 為 store.state.count1
count1,
//'count'等同于 ‘state => state.count’
countAlias: 'count',
countPlus (state){
// 使用普通函數是為了保證this指向元件對象
return state.count + this.localState;
}
})
}
//上面是通過mapState的對象來指派的,還可以通過mapState的數組來指派
computed: mapState(['count']);
//這種方式很簡潔,但是元件中的state的名稱就跟store中映射過來的同名
對象擴充運算符
mapState 函數傳回的是一個對象,為了将它裡面的計算屬性與元件本身的局部計算屬性組合起來,需要用到對象擴充運算符。
computed: {
localState () {
... mapState ({
})
}
這樣,mapState中的計算屬性就與localState計算屬性混合一起了。
3.3 getters
有時候我們需要從 store 中的 state 中派生出一些狀态,例如對清單進行過濾并計數。此時可以用到getters,getters可以看作是store的計算屬性,其參數為state。
const store = new Vuex.Store({
state: {
todos: [
{id: 1, text: 'reading', done: true},
{id: 2, text: 'playBastketball', done: false}
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done);
}
}
});
3.3.1 擷取getters裡面的狀态,方法一
store.getters.doneTodos // [{ id: 1, text: 'reading', done: true }]
//在元件中,則要寫在計算屬性中,
computed: {
doneTodos () {
return this.$store.getters.doneTodos;
}
}
3.3.2 使用mapGetters擷取getters裡面的狀态:方法二
import {mapState, mapGetters} from 'vuex';
computed: {
...mapState(['increment']),
...mapGetters(['doneTodos'])
}
3.4 mutations
mutations裡面是如何更改state中狀态的邏輯。更改Vuex中的state的唯一方法是,送出mutation,即store.commit(‘increment’)。
3.4.1 送出載荷(payload)
可以向commit傳入額外的參數,即mutation的載荷。
mutations: {
increment(state, n){
state.count += n;
}
}
store.commit('increment', 10);
payload還可以是一個對象。
mutations: {
increment(state, payload)
state.count += payload.amount;
}
}
store.commit('increment', {amount: 10});
還可以使用type屬性來送出mutation。
store.commit({
type: 'increment',
amount: 10
});
// mutations保持不變
mutations: {
increment(state, payload){
state.count += payload.amount;
}
}
注意:mutation必須是同步函數,不能是異步的,這是為了調試的友善。
3.4.2 在元件中送出mutations
那麼mutation應該在哪裡送出呢? 因為js是基于事件驅動的,是以改變狀态的邏輯肯定是由事件來驅動的,是以store.commit(‘increment’)是在元件的methods中來執行的。
方法1: 在元件的methods中送出
methods: {
increment(){
this.$store.commit('increment');
}
}
方法2: 使用mapMutaions
用 mapMutations 輔助函數将元件中的 methods 映射為 store.commit 調用。
import { mapMutaions } from 'vuex';
export default {
// ...
methods: {
...mapMutaions([
'increment' // 映射 this.increment() 為 this.$store.commit('increment')
]),
...mapMutaions([
add: 'increment' // 映射 this.add() 為 this.$store.commit('increment')
])
}
}
// 因為mutation相當于一個method,是以在元件中,可以這樣來使用
<button @click="increment">+</button>
3.5 actions
因為mutations中隻能是同步操作,但是在實際的項目中,會有異步操作,那麼actions就是為了異步操作而設定的。這樣,就變成了在action中去送出mutation,然後在元件的methods中去送出action。隻是送出actions的時候使用的是dispatch函數,而mutations則是用commit函數。
3.5.1 一個簡單的action
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state){
state.count++;
}
},
actions: {
increment(context){
context.commit('increment');
}
/* 可以用參數結構的方法來寫action
increment({commit}){
commit('increment');
}
*/
}
});
// action函數接受一個context參數,這個context具有與store執行個體相同的方法和屬性。
// 分發action
store.dispatch('increment');
action同樣支援payload和對象方式來分發,格式跟commit是一樣的,不再贅述。
4.5.2 在元件中分發action
方法1: 在元件的methods中,使用this.$store.dispatch(‘increment’)。
方法2: 使用mapActions,跟mapMutations是類似的。
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment' // 映射 this.increment() 為 this.$store.dispatch('increment')
]),
...mapActions({
add: 'increment' // 映射 this.add() 為 this.$store.dispatch('increment')
})
}
}
// 同樣在元件中,可以這樣來使用
<button @click="increment">+</button>
3.5.3 組合actions
因為action是異步的,那麼我們需要知道這個異步函數什麼時候結束,以及等到其執行後,會利用某個action的結果。這個可以使用promise來實作。在一個action中傳回一個promise,然後使用then()回調函數來處理這個action傳回的結果。
actions:{
actionA({commit}){
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation');
resolve();
},1000);
})
}
}
// 這樣就可以操作actionA傳回的結果了
store.dispatch('actionA').then(() => {
// dosomething ...
});
// 也可以在另一個action中使用actionA的結果
actions: {
// ...
actionB({ dispatch, commit }){
return dispatch('actionA').then(() => {
commit('someOtherMutation');
})
}
}
4 mudules
module是為了将store拆分後的一個個小子產品,這麼做的目的是因為當store很大的時候,分成子產品的話,友善管理。
4.1 每個module擁有自己的state, getters, mutation, action
const moduleA = {
state: {...},
getters: {...},
mutations: {....},
actions: {...}
}
const moduleB = {
state: {...},
getters: {...},
mutations: {....},
actions: {...}
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
});
store.state.a // 擷取moduleA的狀态
store.state.b // 擷取moduleB的狀态
4.2 子產品内部的狀态
對于子產品内部的mutation和getter,接受的第一個參數是子產品的局部狀态state。順便說一下,根結點的狀态為rootState。
const moduleA = {
state: { count: 0},
getters: {
doubleCount(state){
return state.count * 2;
}
},
mutations: {
increment(state){
state.count ++ ;
}
},
actions: {...}
}
4.3 子產品的動态注冊
在子產品建立之後,可以使用store.registerModule方法來注冊子產品。
store.registerModule('myModule', {
// ...
});
依然的,可以通過store.state.myModule來擷取子產品的狀态。
可以使用store.unregisterModule(moduleName)來動态的解除安裝子產品,但是這種方法對于靜态子產品是無效的(即在建立store時聲明的子產品)。
5 含有vuex的項目的結構
5.1 應該遵循的規則
- 1. 應用層級的狀态都應該集中在store中
- 2. 送出 mutation 是更改狀态state的唯一方式,并且這個過程是同步的。
- 3. 異步的操作應該都放在action裡面
如果對您有所幫助,歡迎您點個關注,我會定時更新技術文檔,大家一起讨論學習,一起進步。