Vuex和localStorage、sesstionStorage的區別,應用場景。vuex的State、Getter、Mutation、Action、Module等技術知識點掌握。 ...
菜單快捷導航
- Vuex是什麼東東,有什麼應用場景?localStorage和sessionStorage能否替代它?
- Vuex知識點State、Getter、Mutaion、Action
- Vuex模塊化(Module)
1、Vuex概念和應用場景
首先,Vuex是什麼,官網介紹說Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式。我的理解就是Vuex就是類似於sessionStorage這樣管理數據(本地存和取)的一種技術方案。
既然vuex類似於sessionStorage,那為何我們還要學習vuex,直接用sessionStorage和localStorage不就好了?這個問得好,我來描述一種場景:多個視圖(view)組件都要用到某一條數據(狀態),當這條數據發生變化的時候,依賴於該數據(狀態)的相關視圖(view)都要跟著即時更新。這種場景在工作中非常常見,我說一個自己碰到的例子,以前有一個react項目,其中有個功能是在pc頁面自定義小程式頁面,然後整個PC頁面有三個組件組成,在三個組件中還有其他的很多子組件。然後一開始的做法就是通過事件和組件間傳值來進行整個頁面數據同步更新,後面隨著組件越來越多,功能越來越複雜,麻煩和問題也就越來越多。然後每一個後面來接手的同事看代碼都要看好一陣,長痛不如短痛...
對的,在工作中這種常見的多個組件依賴於同一條數據(狀態),需要即時響應更新的情況,vuex的價值就體現出來了。這種情況下,vuex相比其他實現手段,就要簡單干脆方便多了!先看一個小例子,看看vuex和localStorage、sessionStorage的區別,上圖:
如圖,vuexPageA頁面中引用了三個組件,每個組件都分別從localStorage、sessionStorage、vuex中取了一個值。點擊按鈕加1的時候,vuex的值是及時更新了,其他需要刷新才能更新。總結一下:
- localStorage存儲的值能夠永久的存儲在瀏覽器上。不管是重新打開新視窗還是重啟,同一個瀏覽器上的相同功能變數名稱下,localStorage的值一直在。
- sessionStorage存儲的值依賴於當前視窗(當前會話), 只要當前視窗不關閉,它存儲的數據就一直在。一旦關閉視窗或者打開新視窗,sessionStorage之前存儲的數據就會消失。
- 相比localStorage和sessionStorage,vuex存儲的數據可以即時更新到,當前項目下的所有引用了該數據的組件。但是如果刷新頁面的話,vuex存儲的值會重置,而localStorage和sessionStorage存儲的值不會重置。
相關代碼見:https://github.com/xiaotanit/tan_vue/blob/master/src/views/vuex/VuexPageA.vue
2、Vuex知識點State、Getter、Mutaion、Action
2.1 Vuex之State和mapState
每一個Vuex應用的核心就是store(倉庫),“store"基本上就是一個容器。Vuex使用單一狀態樹,相當於用一個對象(store)就包含了全部的應用層級狀態,也就是說每個應用也只包含一個store實例。因此Vuex的使用從new一個Vuex.Store實例(store實例)開始。store實例中的State屬性就是用來存放Vue應用的所有的狀態。先來看要給最簡單的包含State屬性的store實例:
import Vuex from 'vuex' import Vue from 'vue' Vue.use(Vuex) export default new Vuex.Store({ state: { count: 0, }, })
後面的mutations、getters、actions再慢慢往裡面加入代碼。
store實例創建,如何應用?Vue實例創建時,提供了一個store選項,可以讓Vuex通過store選項,將store實例對象從根組件”註入“到每一個子組件中:
import Vue from 'vue' import App from './App.vue' import router from './router.js' //vuex 之 store實例對象 import store from './api/store/index' new Vue({ router, store, // 把 store 對象提供給 “store” 選項,這可以把 store 的實例註入所有的子組件 render: h => h(App) }).$mount('#app')
store實例註入根組件後,應用中的每個組件中通過this.$store指的就是該store實例對象。那麼現在如何在Vue組件中展示store中的state狀態(數據)呢?由於Vuex的狀態存儲是即時響應的,從store實例中讀取狀態最簡單的方法就是在Vue組件中”計算屬性“computed中返回某個狀態。每當store.state中某個狀態變化的時候,都會重新求取計算屬性,並且觸發更新相關聯的DOM。
mapState是一個輔助函數,當我們應用中一個組件需要獲取store中多個狀態的時候,使用mapState輔助函數可以幫助我們更加方便生成計算屬性。看看下麵的應用測試代碼:
import { mapState } from 'vuex'; export default { data(){ return { localCount: 88 } }, mounted(){ console.log("...store對象:", this.$store); }, computed:{ localStorage_count(){ return localStorage.getItem('localStorage_count') }, //使用對象展開符"...",可以將對象目標對象混入到外部對象中 ...mapState({ sessionStorage_count(){ return sessionStorage.getItem('sessionStorage_count') }, vuex_count: state => state.count, //箭頭函數可以使代碼更簡練 vuex_count_alias: 'count', //傳字元串參數'count'等同於 state => state.count // 為了能夠使用 `this` 獲取局部狀態,必須使用常規函數 countPlusLocalState (state) { return state.count + this.localCount } }), }, }View Code
2.2 Vuex之Getter和mapGetters
有時我們需要從store中的state種派生出一些狀態,比如對store中的某一個狀態(數據)進行篩選過濾,然後特別是當有多個組件需要用到這種狀態(數據)時,“getter"就出場了!Vuex允許我們在store中定義”getter"(可以認為是store對象的計算屬性)。就像計算屬性一樣,getter的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變才會被重新計算。Getter接受state作為其第一個參數:
export default new Vuex.Store({ state: { count: 0, todos: [ { id: 1, text: '金戈鐵馬,氣吞萬里如虎', done: true }, { id: 2, text: '老驥伏櫪,志在千里', done: false }, { id: 3, text: '周公吐哺,天下歸心', done: true }, { id: 4, text: '但使龍城飛將在,不教胡馬度陰山', done: false }, ] }, //Vuex允許我們再store中定義"getter"(可以認為是store的計算屬性)。 // 就像計算屬性一樣,getter的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變才會被重新計算。 getters: { doneTodos: state => { console.log('...state.getters.donwTodos...') return state.todos.filter(todo => todo.done) }, //Getter也可以接受其他getter作為第二個參數 //getter在通過屬性訪問時是作為Vue的響應式系統的一部分緩存其中的 doneTodosCount: (state, getters) => { console.log('...state.getters.doneTodosLength...', getters.doneTodos) return getters.doneTodos.length; }, //通過方法訪問:通過讓getter返回一個函數,來實現給getter傳參。 //getter在通過方法訪問時,每次都會去進行調用,而不會緩存結果。 getTodoById: (state) => (id) => { console.log('...state.getters.getTodoById...: ', id); return state.todos.find(todo => todo.id === id); } }, })View Code
Getter應用:Getter會暴露為 store.getters 對象,然後在組件中,我們可以通過this.$store.getters來得到getter。getter裡面的屬性,可以返回屬性,也可以返回方法。如果getter通過屬性訪問時是作為Vue的響應式系統的一部分緩存,首次調用後再次調用時就會調用緩存,只有該屬性的依賴值變化時,再次調用該屬性才會重新調用重新緩存。如果getter通過方法訪問時,每次都會去進行調用,而不會緩存結果。組件中應用測試代碼:
methods:{ //state.getters調用 stateGettersProperty(){ //getters屬性調用, 屬性調用會被緩存 console.log(this.$store.getters.doneTodos); console.log(this.$store.getters.doneTodosCount); }, stateGettersMethod(){ //方法調用,每次都會去進行調用,而不會緩存結果。 console.log(this.$store.getters.getTodoById(2).text); console.log(this.$store.getters.getTodoById(3).text); }, addTodo(){ //增加數據 let count = this.$store.state.todos.length; let obj = { id: count + 1, text: (count+1) + '***' + (count+1), done: count % 2 } this.$store.commit('addTodos', obj); }, }View Code
mapGetters也是一個輔助函數,可以將store對象中的getter映射到局部計算屬性:
import { mapGetters } from 'vuex' export default { // ... computed: { // 使用對象展開運算符將 getter 混入 computed 對象中 ...mapGetters([ 'doneTodosCount', 'anotherGetter', // ... ]) } }View Code
如果你想將一個 getter 屬性另取一個名字,使用對象形式:
mapGetters({ // 把 `this.doneCount` 映射為 `this.$store.getters.doneTodosCount` doneCount: 'doneTodosCount' })View Code
2.3 Vuex之Mutation和mapMutations
上面說的mapState、getters、mapGetters都是對store對象中的狀態(state)進行應用,如果想更改Vuex的store對象中的狀態(state),必須要用mutation。Vuex中的mutation非常類似於事件:每個mutation都有一個字元串的事件類型(type)和一個回調函數(handler) 。這個回調函數就是我們實際進行狀態更改的地方,並且它會接受state作為第一個參數:
const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { // 變更狀態 state.count++ } } })View Code
mutation裡面handler調用通過store.commit來調用,調用方式有“載荷(payload)"和“對象風格”兩種方式:
- 載荷提交方式:
// ... mutations: { increment (state, n) { state.count += n } }
store.commit('increment', 10)
- 對象風格提交方式:
mutations: { increment (state, payload) { state.count += payload.amount } }
store.commit({ type: 'increment', amount: 10 })
一條重要的原則:mutation必須是同步函數。在組件中使用this.$store.commit('***')提交mutation,或者使用mapMutations輔助函數將組件中的methods映射為store.commit調用。
2.4 Vuex之Action和mapActions
Action類似於mutation,但是Action提交的是mutation,不能直接變更狀態;另外Action可以包含任意非同步操作。在組件中使用this.$store.dispatch('***')調用action,或者使用mapActions輔助函數將組件中的methods映射為store.dispatch調用。
State、Getter、Mutation、Action的一些應用測試代碼見:https://github.com/xiaotanit/tan_vue/blob/master/src/views/vuex/VuexPageB.vue
3、Vuex之模塊化(Module)
由於使用單一狀態樹,應用的所有狀態(數據)會集中到一個比較大的對象。當應用變得非常複雜時,store對象就有可能變得相當臃腫。為瞭解決這種問題,Vuex允許我們將store分隔成模塊(module)。每個模塊都有自己的state、mutation、action、getter、甚至是嵌套子模塊。
預設情況下,模塊內容的action、mutation和getter是註冊在全局命名空間的,這樣使得多個模塊能夠對同一mutation或action作出響應。因此為了讓模塊具有更高的封裝度和復用性,我們可以在每個子模塊中添加namespaced: true屬性,這樣表示該模塊成為了帶命名空間的模塊。這樣後面再調用該模塊的getter、action和mutation時需要帶上該模塊名稱+調用的屬性或方法。下麵寫一個示例代碼:
新建三個js文件moduleA.js、moduleB.js、moduleStore.js,其中moduleA和moduleB分別為子模塊。
moduleA.js:
const state = { countA: 99 } const mutations = { increment(state){ state.countA++ }, decrement(state){ state.countA-- } } const getters = { doubleCount(state){ return state.countA * 2 } } const actions = { add({ commit }){ setTimeout(function(){ commit('increment') }, 50) }, minus({ commit }){ setTimeout(()=>{ commit('decrement') }, 500) } } export default { namespaced: true, //表示設置命名空間 state, mutations, getters, actions }View Code
moduleB.js:
const state = { countB: 11 } const mutations = { increment(state){ state.countB++; }, decrement(state){ state.countB--; } } //getters類似state裡面屬性的計算屬性 const getters = { doubleCount(state){ return state.countB * 2; } } const actions = { add({ commit }){ commit('increment') }, minus({ commit }){ commit('decrement') } } export default { namespaced: true, state, getters, mutations, actions }View Code
moduleStore.js:
/* * 當項目大了後,為了責任清晰,目標明確,更易管理,將store拆成多個module形式 * */ import moduleCountA from './moduleA' import moduleCountB from './moduleB' import vuex from 'vuex' import vue from 'vue' vue.use(vuex) export default new vuex.Store({ modules: { moduleCountA, moduleCountB } })
再新建一個VuexPageC.vue頁面,測試調用,js代碼如下:
import { mapGetters, mapActions, mapMutations } from 'vuex' export default { computed:{ ...mapGetters({ doubleCountA: 'moduleCountA/doubleCount', doubleConunB: 'moduleCountB/doubleCount' }) }, methods: { ...mapActions({ //moduleA模塊的actions addCountA: 'moduleCountA/add', minusCountA: 'moduleCountA/minus', //moduleB模塊的actions addCountB: 'moduleCountB/add', minusCountB: 'moduleCountB/minus' }), ...mapMutations({ //moduleA模塊的mutions incrementA: 'moduleCountA/increment', decrementA: 'moduleCountA/decrement', //moduleB模塊的mutions incrementB: 'moduleCountB/increment', decrementB: 'moduleCountB/decrement' }), } }
頁面效果如圖:
完整VuexPageC.vue頁面代碼見:https://github.com/xiaotanit/tan_vue/blob/master/src/views/vuex/VuexPageC.vue