Vuex模塊化應(yīng)用實(shí)踐示例
Vuex作為Vue全家桶的成員之一,重要性肯定不用多說,正在做Vue項(xiàng)目的同學(xué),隨著項(xiàng)目需求、功能逐漸增加,用到Vuex也是早晚的事兒,作為一個(gè)前端,只能面對現(xiàn)實(shí):學(xué)不動也得學(xué)!
這篇文章主要介紹Vuex在大型項(xiàng)目中的模塊化及持久化應(yīng)用實(shí)踐,下面正文開始
Vuex的應(yīng)用場景
- 多個(gè)組件視圖共享同一狀態(tài)時(shí)(如登錄狀態(tài)等)
- 多個(gè)組件需要改變同一個(gè)狀態(tài)時(shí)
- 多個(gè)組件需要互相傳遞參數(shù)且關(guān)系較為復(fù)雜,正常傳參方式變得難以維護(hù)時(shí)
- 持久化存儲某些數(shù)據(jù)
所以我們把組件共享的狀態(tài)抽離出來,不管組件間的關(guān)系如何,都通過Vuex來處理
組織store目錄
我們先按模塊化的方式組織store目錄,并在Vue根實(shí)例中注冊store,Vuex 通過 store 選項(xiàng),提供了一種機(jī)制將狀態(tài)從根組件“注入”到每一個(gè)子組件中
src ├── ... ├── main.js ├── App.vue └── store ├── index.js # 我們組裝模塊并導(dǎo)出 store 的地方 └── modules ├── product.js # 產(chǎn)品模塊 ├── windowInfo.js # 窗口信息模塊 └── user.js # 登錄模塊
src/store/index.js
import Vue from 'vue' import Vuex from 'vuex' import user from './modules/user' import product from './modules/product' import windowInfo from './modules/windowInfo' Vue.use(Vuex) export default new Vuex.Store({ modules: { // 注冊modules中的模塊 user, product, windowInfo } })
src/main.js
import ... import store from './store' // 添加這行 new Vue({ el: '#app', router, store, // 注入到根實(shí)例 template: '<App/>', components: { App } })
store的屬性
state(狀態(tài)對象)
state中存放多頁面共享的狀態(tài)字段
getters
相當(dāng)于當(dāng)前模塊state的計(jì)算屬性
mutations
如果想更新state中的字段,提交mutations中定義的事件是唯一的方式(key為事件名,value是一個(gè)函數(shù)),但是這個(gè)事件函數(shù)必須是同步執(zhí)行的
actions
可以定義異步函數(shù),并在回調(diào)中提交mutation,就相當(dāng)于異步更新了state中的字段
vuex數(shù)據(jù)傳遞規(guī)則
使用方法
把窗口的高度和寬度存到Vuex中,并且每當(dāng)窗口被resize,state中的高度和寬度自動更新
src/store/modules/windowInfo.js
import { MU_WIN_RESIZE } from '../../common/constants' const windowInfo = { state: { // 初始化 winHeight: 0, winWidth: 0 }, mutations: { // 這里把事件名統(tǒng)一抽離到constants.js統(tǒng)一管理,方便維護(hù),避免重復(fù)。 // 當(dāng)然,你也可以不這么寫。。 // mutation事件接受的第一個(gè)參數(shù)是當(dāng)前模塊的state對象 // 第二個(gè)參數(shù)是提交事件時(shí)傳遞的附加參數(shù) [MU_WIN_RESIZE] (state, payload) { const { winWidth, winHeight } = payload state.winWidth = winWidth state.winHeight = winHeight } }, actions: {}, getters: {} } export default windowInfo
src/common/constants.js
export const MU_WIN_RESIZE = 'MU_WIN_RESIZE' // 更新窗口尺寸
下面打開項(xiàng)目的根組件添加監(jiān)聽resize事件和提交mutation事件邏輯
src/App.vue
<!--上面的template我就不往這兒放了--> <script> import { _on, _off, getClientWidth, getClientHeight } from './common/dom' import { MU_WIN_RESIZE } from './common/constants' import { mapMutations } from 'vuex' export default { name: 'app', data () { return {} }, mounted () { this.handleResize() // 這里對addEventListener方法做了IE兼容處理,就不貼出來了,反正事件監(jiān)聽你們都會 _on(window, 'resize', this.handleResize) }, beforeDestroy () { _off(window, 'resize', this.handleResize) }, methods: { // 對象展開運(yùn)算符,不熟悉的同學(xué)該學(xué)學(xué)ES6了 ...mapMutations({ // 映射 this.winResize 為 this.$store.commit(MU_WIN_RESIZE) winResize: MU_WIN_RESIZE }), handleResize () { const winWidth = getClientWidth() const winHeight = getClientHeight() this.winResize({ winWidth, winHeight }) } } } </script>
到這一步,在拖動窗口觸發(fā)‘resize'事件的時(shí)候,就會觸發(fā)‘MU_WIN_RESIZE'這個(gè)mutation事件并把窗口寬高寫入vuex,下面我們隨便找個(gè)頁面看能不能獲取到我們寫入的值
<template> <div class="row">窗口高:{{winHeight}} 窗口寬:{{winWidth}}</div> </template> <script> import { mapState } from 'vuex' export default { name: 'test', data () { return {} }, computed: { // 把state寫入計(jì)算屬性 // 如果使用mapGetters也是寫入計(jì)算屬性 ...mapState({ winHeight: state => state.windowInfo.winHeight, winWidth: state => state.windowInfo.winWidth }) }, } </script>
有的時(shí)候我們會從后端獲取一些下拉框選項(xiàng)的靜態(tài)常量,而且很多頁面都能用到,這個(gè)時(shí)候用Vuex是比較好的選擇,涉及到后端獲取,就要用到可以使用異步的actions了
src/store/modules/product.js
import {MU_PRODUCT_UPDATE_CONSTANTS} from '../../common/constants' const product = { state: { productConstants: [] }, mutations: { [MU_PRODUCT_UPDATE_CONSTANTS] (state, payload) { state.productConstants = payload } }, actions: { // action函數(shù)第一個(gè)參數(shù)接受一個(gè)與 store 實(shí)例具有相同方法和屬性的 context 對象, // 因此你可以調(diào)用 context.commit 提交一個(gè) mutation, // 或者通過 context.state 和 context.getters 來獲取 state 和 getters // 這里雖然能獲取到state,但是不建議直接修改state中的字段 async getProductConstants ({ commit }, payload) { try { // 請求接口,如果需要參數(shù)可以通過payload傳遞 const res = await this.$api.product.getConstants() commit(MU_PRODUCT_UPDATE_CONSTANTS, res) } catch (e) { console.error(e) } } }, getters: {} } export default product
下面觸發(fā)這個(gè)getProductConstants事件,觸發(fā)這個(gè)action事件的位置需要注意一下,假設(shè)你有5個(gè)組件需要使用這個(gè)state,那就應(yīng)該在這5個(gè)組件共同的父組件中調(diào)用一次action(找不到就在根實(shí)例中調(diào)用),然后在各個(gè)子組件中通過mapState或mapGetters獲取state,千萬不要每個(gè)組件使用前都調(diào)用一次action方法!
src/App.vue
<!--為了更直觀的展示action,把之前的代碼刪掉了--> <script> import { mapActions } from 'vuex' // 注意是mapActions export default { name: 'app', data () { return {} }, created () { // 觸發(fā)請求 this.getProductConstants() } methods: { ...mapActions([ // 映射 this.getProductConstants 為 this.$store.dispatch('getProductConstants') 'getProductConstants' ]) } } </script>
mapGetters, mapMutations, mapActions,這幾個(gè)函數(shù)可以接受對象也可以接受數(shù)組作為參數(shù),如果你需要在組件中以別的名字調(diào)用該事件(像上面的mapMutations)就可以傳入對象,key為新命名,value是store中定義的名字;否則的話傳數(shù)組就好了。
那么問題來了,既然是異步操作,我想在操作結(jié)束后干點(diǎn)兒別的怎么做呢?
很簡單,調(diào)用action中的異步函數(shù)(this.$store.dispatch)返回的是一個(gè)Promise,如果你跟我一樣用的是async await:
<!--為了更直觀的展示action,把之前的代碼刪掉了--> <script> import { mapActions } from 'vuex' // 注意是mapActions export default { name: 'app', data () { return {} }, async created () { // 觸發(fā)請求 await this.getProductConstants() // 接下來執(zhí)行的操作會等待上面函數(shù)完成才會執(zhí)行 } methods: { ...mapActions([ // 映射 this.getProductConstants 為 this.$store.dispatch('getProductConstants') 'getProductConstants' ]) } } </script>
如果你用的不是async await那就麻煩一點(diǎn),在actions中定義事件的時(shí)候return一個(gè)new Promise,官方文檔中有一個(gè)例子
表單處理
當(dāng)你把從state中獲取的字段填在v-model中時(shí),如果用戶修改表單數(shù)據(jù),v-model會嘗試直接修改store中的數(shù)據(jù),這樣做會有兩個(gè)問題:
- 破壞了vuex的數(shù)據(jù)傳遞規(guī)則,如果想修改state中的數(shù)據(jù)只能通過提交一個(gè)mutation
- 控制臺報(bào)錯(cuò):計(jì)算屬性沒有setter
官方提供了兩種解決方法,我更傾向于下面這種,給計(jì)算屬性添加setter,并在setter中提交mutation修改state:
<template> <input v-model="message"> </template> <script> export default { name: 'app', data () { return {} }, computed: { message: { get () { return this.$store.state.test.message }, set (value) { this.$store.commit('updateMessage', value) } } } methods: {} } </script>
Vuex持久化
推薦插件vuex-persist
安裝插件:
npm install --save vuex-persist
引入、配置、加載插件:
src/store/persist.js
import VuexPersistence from 'vuex-persist' const persist = new VuexPersistence({ // 其他參數(shù)看文檔 storage: window.sessionStorage }) export default persist.plugin
src/store/index.js
import ... import persist from './persist' Vue.use(Vuex) export default new Vuex.Store({ modules: { user, product, windowInfo }, plugins: [persist] })
現(xiàn)在刷新瀏覽器數(shù)據(jù)也不會重置了!
總結(jié)
以上就是vuex比較常規(guī)的操作了,第一次看官方文檔的我是懵逼的、無助的,但是用了一段時(shí)間vuex再重新看文檔的時(shí)候會有很多收獲。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JavaScript簡單實(shí)現(xiàn)網(wǎng)頁回到頂部功能
JavaScript簡單實(shí)現(xiàn)網(wǎng)頁回到頂部功能,大家可以參考一下2013-11-11JavaScript canvas復(fù)刻蘋果發(fā)布會環(huán)形進(jìn)度條
canvas 真是一個(gè)好東西,它給前端插上了想象的翅膀,伴隨著 h5 而來,將 web 代入了新的領(lǐng)域。本文將利用anvas復(fù)刻蘋果發(fā)布會環(huán)形進(jìn)度條,感興趣的可以嘗試一下2022-07-07zTree實(shí)現(xiàn)節(jié)點(diǎn)修改的實(shí)時(shí)刷新功能
在實(shí)際應(yīng)用中會遇到動態(tài)操作樹各節(jié)點(diǎn)的需求,在增加樹節(jié)點(diǎn)后如何實(shí)時(shí)動態(tài)刷新樹就十分有必要了。這篇文章主要介紹了zTree實(shí)現(xiàn)節(jié)點(diǎn)修改的實(shí)時(shí)刷新功能,需要的朋友可以參考下2017-03-03JavaScript函數(shù)式編程實(shí)現(xiàn)介紹
函數(shù)式編程是一種編程范式,將整個(gè)程序都由函數(shù)調(diào)用以及函數(shù)組合構(gòu)成。 可以看成一條流水線,數(shù)據(jù)可以不斷地從一個(gè)函數(shù)的輸出流入另一個(gè)函數(shù)的輸入,最后輸出結(jié)果2022-09-09

利用uni-app和uView實(shí)現(xiàn)多圖上傳功能全過程

JS函數(shù)進(jìn)階之prototy用法實(shí)例分析