本文深入解析Vue项目中Vuex的模块化组织与持久化方案。通过实际代码示例,演示如何拆分store目录、注册模块、处理异步Action及表单双向绑定冲突。重点介绍使用vuex-persist插件实现刷新不丢失数据,帮助开发者构建可维护的大型Vue应用状态管理架构。
Vuex模块化与持久化实战指南
Vuex是Vue生态系统中不可或缺的核心成员,其重要性不言而喻。对于正在开发Vue项目的开发者而言,随着业务需求与功能模块的不断增加,引入Vuex进行状态管理几乎是必然的选择。尽管学习曲线存在挑战,但前端开发者必须掌握这一关键技术以应对复杂的工程需求。
本文将重点探讨Vuex在大型Vue项目中的模块化拆分策略及数据持久化存储的最佳实践,以下是具体的实施步骤与代码解析。
Vuex应用场景分析
Vuex主要适用于以下几种典型场景:
- 当多个组件视图需要共享同一份状态数据时,例如全局登录状态、用户信息等。
- 当多个组件需要共同修改同一个状态字段时,通过集中管理可避免数据不一致。
- 当组件间参数传递关系复杂,传统的props或emit传参方式导致代码难以维护时。
- 需要持久化存储某些关键数据,确保页面刷新后数据不丢失。
基于上述场景,我们将组件共享的状态统一抽离至Vuex中管理。无论组件间的层级关系如何复杂,所有状态交互均通过Vuex进行统一处理,从而提升代码的可维护性与清晰度。
Store目录组织与注册
接下来,我们将详细讲解如何组织store目录结构。
首先,按照模块化的方式构建store目录,并在Vue根实例中注册store。Vuex通过store选项提供了一种机制,将状态从根组件“注入”到每一个子组件中,使得所有子组件都能访问到全局状态。
src
├── ...
├── main.js
├── App.vue
└── store
├── index.js # 我们组装模块并导出 store 的地方
└── modules
├── product.js # 产品模块
├── 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, // 注入到根实例
template: '<App/>',
components: { App }
})
Vuex核心属性解析
Vuex核心包含以下关键属性:
state(状态对象):用于存放多页面共享的状态字段,是数据的唯一来源。
getters:相当于当前模块state的计算属性,用于派生出一些基于state的状态。
mutations:若需更新state中的字段,提交mutations中定义的事件是唯一合法方式(key为事件名,value为函数)。注意,mutation处理函数必须是同步执行的。
actions:允许定义异步函数,并在回调中提交mutation,从而实现异步更新state中的字段。

Vuex数据传递遵循严格规则:只能通过mutation修改state,通过dispatch触发action。
Vuex使用方法与案例
以下是Vuex的具体使用方法示例。
我们将窗口的高度和宽度存储到Vuex中,并监听window resize事件,确保state中的高度和宽度随窗口大小变化自动更新。
src/store/modules/windowInfo.js
import { MU_WIN_RESIZE } from '../../common/constants'
const windowInfo = {
state: {
// 初始化
winHeight: 0,
winWidth: 0
},
mutations: {
// 这里把事件名统一抽离到constants.js统一管理,方便维护,避免重复。
// 当然,你也可以不这么写。。
// mutation事件接受的第一个参数是当前模块的state对象
// 第二个参数是提交事件时传递的附加参数
[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' // 更新窗口尺寸
接下来,在项目的根组件中添加监听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兼容处理,就不贴出来了,反正事件监听你们都会
_on(window, 'resize', this.handleResize)
},
beforeDestroy () {
_off(window, 'resize', this.handleResize)
},
methods: {
// 对象展开运算符,不熟悉的同学该学学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>
至此,当拖动窗口触发‘resize'事件时,便会触发‘MU_WIN_RESIZE'这个mutation事件,并将窗口宽高写入vuex。我们可以随意选择一个页面来验证是否能获取到写入的值。
<template>
<div class="row">窗口高:{{winHeight}} 窗口宽:{{winWidth}}</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'test',
data () {
return {}
},
computed: {
// 把state写入计算属性
// 如果使用mapGetters也是写入计算属性
...mapState({
winHeight: state => state.windowInfo.winHeight,
winWidth: state => state.windowInfo.winWidth
})
},
}
</script>
有时我们需要从后端获取一些下拉框选项的静态常量,且这些常量在多个页面中通用。此时使用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函数第一个参数接受一个与 store 实例具有相同方法和属性的 context 对象,
// 因此你可以调用 context.commit 提交一个 mutation,
// 或者通过 context.state 和 context.getters 来获取 state 和 getters
// 这里虽然能获取到state,但是不建议直接修改state中的字段
async getProductConstants ({ commit }, payload) {
try {
// 请求接口,如果需要参数可以通过payload传递
const res = await this.$api.product.getConstants()
commit(MU_PRODUCT_UPDATE_CONSTANTS, res)
} catch (e) {
console.error(e)
}
}
},
getters: {}
}
export default product
触发getProductConstants事件时,调用位置需谨慎。假设有5个组件需要使用该state,应在这5个组件共同的父组件中调用一次action(若找不到合适父级,则在根实例中调用)。随后,在各子组件中通过mapState或mapGetters获取state。切勿在每个组件使用前都单独调用一次action方法,以免引发重复请求。
src/App.vue
<!--为了更直观的展示action,把之前的代码删掉了-->
<script>
import { mapActions } from 'vuex' // 注意是mapActions
export default {
name: 'app',
data () {
return {}
},
created () {
// 触发请求
this.getProductConstants()
}
methods: {
...mapActions([
// 映射 this.getProductConstants 为 this.$store.dispatch('getProductConstants')
'getProductConstants'
])
}
}
</script>
mapGetters、mapMutations、mapActions这几个辅助函数既可接受对象也可接受数组作为参数。若需在组件中以其他名称调用该事件(如上述mapMutations示例),可传入对象,其中key为新命名,value为store中定义的名字;否则直接传入数组即可。
那么问题来了,既然是异步操作,如何在操作结束后执行其他逻辑呢? 很简单,调用action中的异步函数(this.$store.dispatch)返回的是一个Promise对象。如果你也使用async/await语法,处理起来会非常便捷。
<!--为了更直观的展示action,把之前的代码删掉了-->
<script>
import { mapActions } from 'vuex' // 注意是mapActions
export default {
name: 'app',
data () {
return {}
},
async created () {
// 触发请求
await this.getProductConstants()
// 接下来执行的操作会等待上面函数完成才会执行
}
methods: {
...mapActions([
// 映射 this.getProductConstants 为 this.$store.dispatch('getProductConstants')
'getProductConstants'
])
}
}
</script>
若未使用async/await,则需稍作处理:在actions中定义事件时return一个new Promise。官方文档中提供了相关示例供参考。
表单处理与持久化配置
表单处理是Vuex使用中常见的痛点。
当你将从state中获取的字段绑定到v-model时,若用户修改表单数据,v-model会尝试直接修改store中的数据。这种做法存在两个主要问题:
- 破坏了Vuex的数据传递规则,因为修改state中的数据只能通过提交mutation来实现。
- 控制台报错:计算属性没有setter。
官方提供了两种解决方法,我更倾向于给计算属性添加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({
// 其他参数看文档
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]
})
配置完成后,刷新浏览器数据也不会重置,实现了数据的持久化存储。
总结。
以上就是Vuex比较常规的操作实践。初次阅读官方文档时可能会感到困惑和无助,但在使用一段时间Vuex后重新查阅文档,往往会有更深刻的理解和收获。希望对大家的学习有所帮助,也希望大家多多支持本站。