vuex知識筆記,及與localStorage和sessionStorage的區別
選單快捷導航
- 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