Vuex 狀態(tài)管理模式

Vuex 狀態(tài)管理模式

在使用vue開發(fā)過(guò)程中,經(jīng)常會(huì)遇到一個(gè)狀態(tài)在多個(gè)組件之間使用,這時(shí)候就需要用vuex來(lái)狀態(tài)管理模式來(lái)集中管理解決跨組件通信問(wèn)題,跨組件通信一般是指非父子組件間 的通信。

一、核心概念
在這里插入圖片描述

  • store:vuex使用一個(gè)倉(cāng)庫(kù)store對(duì)象管理應(yīng)用的狀態(tài)和操作過(guò)程,一個(gè) store 包括 state, getter, mutation, action 四個(gè)屬性。vuex里的數(shù)據(jù)都是響應(yīng)式的,任何組件使用同意store的數(shù)據(jù)時(shí),只要store的數(shù)據(jù)變化,對(duì)應(yīng)的組件也會(huì)實(shí)時(shí)更新。
  • state:狀態(tài),vuex的數(shù)據(jù)源。
  • getter:getter就是對(duì)狀態(tài)進(jìn)行處理的提取出來(lái)的公共部分,對(duì)state進(jìn)行過(guò)濾輸出。
  • mutation:提交mutation是更改vuex 的store中的狀態(tài)的唯一方法,并且只能是同步操作。
  • action:對(duì)state的異步操作,并通過(guò)在action提交mutation變更狀態(tài)。
  • module:當(dāng)store對(duì)象過(guò)于龐大時(shí),可以分成多個(gè)module,每個(gè)module也會(huì)有state, getter, mutation, action 四個(gè)屬性。

二、安裝

在使用vue-cli腳手架生成項(xiàng)目使用選擇vuex,也可以在使用以下命令安裝

npm install vuex --save

結(jié)構(gòu)如下:

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

main.js

通過(guò)在main.js注冊(cè) store,該 store 實(shí)例會(huì)注入到根組件下的所有子組件中,且子組件能通過(guò) this.$store 訪問(wèn)到。

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

三、State

state保存狀態(tài),先在state中定義一個(gè)狀態(tài)count

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {},
  actions: {},
  modules: {}
}

現(xiàn)在有一個(gè)集中管理的狀態(tài)count,其他組件可以通過(guò)計(jì)算屬性來(lái)返回此狀態(tài),由于在main.js注冊(cè)過(guò)store,所以可以使用this.$store就可以獲取到state。

<template>
  <div>
    <p>{{count}}</p>
  </div>
</template>

<script>
  export default {
    computed: {
      count() {
        return this.$store.state.count;
      }
    }
 }
</script>

mapState 輔助函數(shù)

當(dāng)一個(gè)組件需要多個(gè)狀態(tài)時(shí),為這些狀態(tài)都做計(jì)算屬性會(huì)很冗余麻煩,可以使用 mapState 輔助函數(shù)來(lái)幫助生成計(jì)算屬性。

<template>
  <div>
    <p>{{count}}</p>
    <p>{{calculation}}</p>
  </div>
</template>

<script>
  import {
    mapState
  } from 'vuex'
  export default {
    data() {
      return {
        msg: "計(jì)數(shù):"
      }
    },
    computed: mapState({

      // 簡(jiǎn)寫
      // count: state => state.count,

      count(state) {
        return state.count
      },


      // 使用組件內(nèi)狀態(tài)+store狀態(tài)
      calculation(state) {
        return this.msg + state.count
      }
    })
  }
</script>

對(duì)象展開運(yùn)算符

...對(duì)象展開運(yùn)算符,...mapState語(yǔ)法糖簡(jiǎn)化了寫法

<template>
  <div>
    <p>{{count}}</p>
  </div>
</template>

<script>
  import {
    mapState
  } from 'vuex'
  export default {
    computed: {
      ...mapState(['count'])
    }
  }
</script>

四、Getter

getter就是對(duì)狀態(tài)進(jìn)行帥選過(guò)濾操作,處理過(guò)后返回給組件使用。

先定義一組list,然后再對(duì)list篩選。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    list: [{
        name: "魯班七號(hào)",
        game: "王者榮耀"
      },
      {
        name: "亞索",
        game: "英雄聯(lián)盟"
      }, 
      {
        name: "德瑪西亞之力",
        game: "英雄聯(lián)盟"
      }, {
        name: "亞瑟",
        game: "王者榮耀"
      },
      {
        name: "阿古朵",
        game: "王者榮耀"
      },
      {
        name: "努努",
        game: "英雄聯(lián)盟"
      }
    ]
  },
  getters: {
    lol: state => {
      return state.list.filter(p => p.game=="英雄聯(lián)盟");
    }
  },
  mutations: {},
  actions: {},
  modules: {}
})

getter會(huì)暴露出store.getters 對(duì)象,可以以屬性的方式訪問(wèn)

<template>
  <div>
    <ul>
      <li v-for="(item, index) in list" :key="index">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
  export default {
    computed: {
      list() {
        return this.$store.getters.lol;
      },
    }
  }
</script>

mapGetters 輔助函數(shù)

mapGetters輔助函數(shù)僅僅是將 store 中的 getter 映射到局部計(jì)算屬性

<template>
  <div>
    <ul>
      <li v-for="(item, index) in list" :key="index">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
  import {
    mapGetters
  } from 'vuex'
  export default {
    computed: {
     ...mapGetters({
         list: 'lol'
     })
    }
  }
</script>

五、Mutation

如果我們想修改store里的state狀態(tài)值時(shí),我們不可以直接在組件內(nèi)去修改,而是通過(guò)提交mutation來(lái)進(jìn)行修改,mutation類似于事件。我們來(lái)實(shí)現(xiàn)一個(gè)加減計(jì)數(shù)。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
   count:0
  },
  getters: {},
  mutations: {
    // 增加
    increase(state){
      state.count++
    },
    // 減少
    decrease(state){
      state.count--
    }
  },
  actions: {},
  modules: {}
})

在組件內(nèi),通過(guò)this.$store.commit方法來(lái)執(zhí)行mutation,

<template>
  <div>
    <input type="button" value="+" @click="increase" />
    <input type="button" value="-" @click="decrease" />
    <p>計(jì)數(shù):{{count}}</p>
  </div>
</template>

<script>
  import {
    mapState
  } from 'vuex'
  export default {
    methods: {
      increase() {
        this.$store.commit('increase');
      },
      decrease() {
        this.$store.commit('decrease');
      }
    },
    computed: {
      ...mapState(['count'])
    }
  }

</script>

Payload 提交載荷

在提價(jià)mutation時(shí)可以傳入額外的參數(shù),即荷載(payload)

例如我想count每次改變自定義值

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0
  },
  getters: {},
  mutations: {
    // 增加
    increase(state, payload) {
      state.count += payload
    },
    // 減少
    decrease(state, payload) {
      state.count -= payload
    }
  },
  actions: {},
  modules: {}
})
<template>
  <div>
    <input type="button" value="+" @click="increase" />
    <input type="button" value="-" @click="decrease" />
    <p>計(jì)數(shù):{{count}}</p>
  </div>
</template>

<script>
  import {
    mapState
  } from 'vuex'
  export default {
    methods: {
      increase() {
        this.$store.commit('increase',10);  //每次加10
      },
      decrease() {
        this.$store.commit('decrease',5); //每次減5
      }
    },
    computed: {
      ...mapState(['count'])
    }
  }
</script>

官網(wǎng)建議大多數(shù)情況下載荷應(yīng)該是一個(gè)對(duì)象,這樣可以包含多個(gè)字段并且記錄的 mutation 會(huì)更易讀:

  mutations: {
    // 增加
    increase(state, payload) {
      state.count += payload.customcount
    },
    // 減少
    decrease(state, payload) {
      state.count -= payload.customcount
    }
  },
    methods: {
      increase() {
        this.$store.commit('increase', {
          customcount: 10
        }); //每次加10
      },
      decrease() {
        this.$store.commit('decrease', {
          customcount: 5
        }); //每次減5
      }
    },

也可以寫成直接包含type屬性,也就是mutations里的事件(例如:increase、decrease)

    methods: {
      increase() {
        this.$store.commit({
          type:"increase",
          customcount: 10
        }); //每次加10
      },
      decrease() {
        this.$store.commit({
          type:"decrease",
          customcount: 5
        }); //每次減5
      }
    },

Mutation 需遵守 Vue 的響應(yīng)規(guī)則

既然 Vuex 的 store 中的狀態(tài)是響應(yīng)式的,那么當(dāng)我們變更狀態(tài)時(shí),監(jiān)視狀態(tài)的 Vue 組件也會(huì)自動(dòng)更新。這也意味著 Vuex 中的 mutation 也需要與使用 Vue 一樣遵守一些注意事項(xiàng):

  1. 最好提前在你的 store 中初始化好所有所需屬性。
  2. 當(dāng)需要在對(duì)象上添加新屬性時(shí),你應(yīng)該
  • 使用 Vue.set(obj, 'newProp', 123), 或者

  • 以新對(duì)象替換老對(duì)象。例如,利用對(duì)象展開運(yùn)算符可以這樣寫:

    state.obj = { ...state.obj, newProp: 123 }
    

例如

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    people:[]
  },
  getters: {},
  mutations: {
    addNewProp(state,payload){
      
      //新增對(duì)象屬性
      // Vue.set(state.people,"name","Tom");

      //用新對(duì)象替換老對(duì)象
      state.people= {...state.people, name: 'Jerry'} 
    }
  },
  actions: {},
  modules: {}
})
<template>
  <div>
    <input type="button" value="新增屬性" @click="addNewProp" />
    <p>{{name}}</p>
  </div>
</template>

<script>
  import {
    mapState
  } from 'vuex'
  export default {
    methods: {
      addNewProp() {
        this.$store.commit('addNewProp');
      }
    },
    computed: {
      name() {
        return this.$store.state.people.name;
      },
    }}
</script>

Mutation必須是同步函數(shù)

官網(wǎng)給的例子,當(dāng)mutation觸發(fā)的時(shí)候,回調(diào)函數(shù)還沒(méi)有被調(diào)用,回調(diào)函數(shù)中進(jìn)行的狀態(tài)的改變都是不可追蹤的。

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

mapMutations 輔助函數(shù)

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可以包含任意異步操作
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0
    },
    getters: {},
    mutations: {
        // 增加
        increase(state) {
            state.count++
        },
        // 減少
        decrease(state) {
            state.count--
        }
    },
    actions: {
        // action函數(shù)接受一個(gè)與store實(shí)例具有相同方法和屬性的context對(duì)象,調(diào)用context.commit 提交一個(gè)mutation
        increase(context) {
            context.commit('increase')
        }
    },
    modules: {}
})

action通過(guò)store.dispatch觸發(fā),action 則會(huì)提交 mutation,mutation 會(huì)對(duì) state 進(jìn)行修改,組件再根據(jù) state 、getter 渲染頁(yè)面。

上邊的action寫法與之前直接提交mutation沒(méi)有什么區(qū)別,但是action內(nèi)部可以執(zhí)行異步操作,而mutation只能是同步操作。

    // 執(zhí)行異步操作
    actions: {
        increas({ commit }) {
            setTimeout(() => {
                commit('increase')
            }, 1000)
        },
        decrease({ commit }) {
            setTimeout(() => {
                commit('decrease')
            }, 1000)
        }
    }

同時(shí)action也是支持荷載方式和對(duì)象方式進(jìn)行分發(fā):

// 以載荷形式分發(fā)
store.dispatch('increase', {
  amount: 10
})

// 以對(duì)象形式分發(fā)
store.dispatch({
  type: 'increase',
  amount: 10
})

在組件中使用this.$store.dispatch('xxx') 分發(fā) action,或者使用mapActions輔助函數(shù)將組件的methods映射為 store.dispatch 調(diào)用,

<template>
  <div>
    <input type="button" value="+" @click="increase" />
    <input type="button" value="-" @click="decrease" />
    <p>計(jì)數(shù):{{count}}</p>
  </div>
</template>

<script>
  import {
    mapActions
  } from 'vuex'
  import {
    mapState
  } from 'vuex'
  export default {
    // ...
    methods: {
      ...mapActions([
        'increase', // 將 `this.increase()` 映射為 `this.$store.dispatch('increment')`
      ]),
      ...mapActions({
        decrease: 'decrease' // 將 `this.decrease()` 映射為 `this.$store.dispatch('decrease')`
      })
    },computed:{
      ...mapState(["count"])
    }
  }
</script>

七、Module

當(dāng)應(yīng)用變得復(fù)雜時(shí),store對(duì)象會(huì)變得非常臃腫,vuex可以store分割成多個(gè)模塊(Module),每個(gè)模塊都擁有自己的state、mutation、action、getter。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)


const moduleA = {
    state: {
        a: 'a'
    },
    mutations: {},
    actions: {},
    getters: {}
}

const moduleB = {
    state: {
        b: 'b'
    },
    mutations: {},
    actions: {},
    getters: {}
}
export default new Vuex.Store({
    state: {

    },
    getters: {},
    mutations: {},
    actions: {},
    modules: { ma: moduleA, mb: moduleB }
})
<template>
  <div>
    <p>{{msg}}</p>
  </div>
</template>

<script>
  import {
    mapActions
  } from 'vuex'
  import {
    mapState
  } from 'vuex'
  export default {
    computed: {
      msg() {
        return this.$store.state.ma.a; 
      }
    }
  }
</script>

對(duì)于模塊內(nèi)部的mutation與getter,接受的第一個(gè)參數(shù)時(shí)模塊的局部狀態(tài)對(duì)象

const moduleA = {
  state: () => ({
    count: 0
  }),
  mutations: {
    increase (state) {
      // 這里的 `state` 對(duì)象是模塊的局部狀態(tài)
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

對(duì)于模塊內(nèi)部的action,局部狀態(tài)可以通過(guò)context.state暴露出來(lái),根節(jié)點(diǎn)狀態(tài)則為context.rootState

const moduleA = {
  actions: {
    increaseIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

模塊內(nèi)部的getter,根節(jié)點(diǎn)狀態(tài)回作為第三個(gè)參數(shù)暴露出來(lái)

const moduleA = {
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

命令空間

默認(rèn)情況下,mutations、actions、getters這些都是注冊(cè)在全局上面的,可以直接調(diào)用,希望你的模塊具有更高的封裝度和復(fù)用性,你可以通過(guò)添加 namespaced: true 的方式使其成為帶命名空間的模塊。當(dāng)模塊被注冊(cè)后,它的所有 getter、action 及 mutation 都會(huì)自動(dòng)根據(jù)模塊注冊(cè)的路徑調(diào)整命名。

首先新建一個(gè)app.js用來(lái)聲明模塊

const state = {
    keyword: "",
};
const mutations = {
    SET_KEYWORD(state, payload) {
        state.keyword = payload
    },
    DEL_KEYWORD(state) {
        state.keyword = ""
    }
};
//action可以提交mutation,在action中可以執(zhí)行store.commit,如果要使用某個(gè)action,需執(zhí)行store.dispath
const actions = {
    setKeyword({ commit }, value) {
        commit("SET_KEYWORD", value);
    },
    delKeyword({ commit }) {
        commit("DEL_KEYWORD");
    },

};
export const app = {
    namespaced: true,
    state,
    mutations,
    actions
};

然后在store.js里面引入

import { app } from './app.js';

export default new Vuex.Store({
  modules: {
    app: app
  },
});

使用action 根據(jù)模塊注冊(cè)的路徑調(diào)用

store.dispatch('app/delKeyword')

在帶命名空間的模塊內(nèi)訪問(wèn)全局內(nèi)容(Global Assets)

如果你希望使用全局 state 和 getter,rootStaterootGetters 會(huì)作為第三和第四參數(shù)傳入 getter,也會(huì)通過(guò) context 對(duì)象的屬性傳入 action。

若需要在全局命名空間內(nèi)分發(fā) action 或提交 mutation,將 { root: true } 作為第三參數(shù)傳給 dispatchcommit 即可。(官網(wǎng)例子)

modules: {
  foo: {
    namespaced: true,

    getters: {
      // 在這個(gè)模塊的 getter 中,`getters` 被局部化了
      // 你可以使用 getter 的第四個(gè)參數(shù)來(lái)調(diào)用 `rootGetters`
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // 在這個(gè)模塊中, dispatch 和 commit 也被局部化了
      // 他們可以接受 `root` 屬性以訪問(wèn)根 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) { ... }
    }
  }
}

平時(shí)開發(fā)時(shí)只用到了這些,關(guān)于更多的module可以去擼官網(wǎng)。

八、項(xiàng)目結(jié)構(gòu)

平時(shí)開發(fā)中使用下面的項(xiàng)目結(jié)構(gòu)

store
    modules
        a.js
        b.js
        c.js
    getters.js
    index.js

a.js示例

const state = {
    keyword: "",
};
const mutations = {
    SET_KEYWORD(state, payload) {
        state.keyword = payload
    },
    DEL_KEYWORD(state) {
        state.keyword = ""
    }
};
const actions = {
    setKeyword({ commit }, value) {
        commit("SET_KEYWORD", value);
    },
    delKeyword({ commit }) {
        commit("DEL_KEYWORD");
    },

};
export default {
    namespaced: true,
    state,
    mutations,
    actions
};

getters.js示例

const getters = {
    keyword: state => state.a.keyword,
};
export default getters;

index.js示例

import Vue from "vue";
import Vuex from "vuex";
import getters from "./getters";
const path = require("path");

Vue.use(Vuex);

const files = require.context("./modules", false, /\.js$/);
let modules = {};
files.keys().forEach(key => {
  let name = path.basename(key, ".js");
  modules[name] = files(key).default || files(key);
});
const store = new Vuex.Store({
  modules,
  getters
});
export default store;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容