浅谈Vue项目中如何进行数据管理,以及Vuex的基本用法。
背景
最近连续3个项目都在使用 Vue 框架进行开发,踩了不少坑,阅读了很多资料,也学到了很多技巧。
昨晚在写 Demo 的时候,发现自己最近项目中使用 Vuex 的方式有些错误。所以特地深入研究了一番,顺便写下这篇文章做个记录。
踩坑
上个项目的数据管理方案为:将整个项目的所有页面按功能点划分为几大模块(根据目录),再将每个模块映射到 Vuex 的modules
,并且通过modules
内使用namespced
来导出模块。具体如下:
vuex入口
伪代码如下:
1 | // src/store/index.js |
1 | // src/store/modules/index.js |
1 | // src/store/modules/user-state.js |
页面使用数据和调用方法
页面通过mapState
辅助函数,导入对应modules
中的数据state
,通过调用mapActions
辅助函数导出的方法来进行数据请求。伪代码如下:
1 | <script> |
缺陷
从数据流动上来说,是完全按照 Vue 规范的数据单向流动进行的。业务上是完全可以跑通的,并且不会出现Bug。
但是会导致 Vuex 缓存的数据过多,页面占用内容增大,每个页面created
后就一直会维持一份自己的数据。进入页面后,若数据返回较慢,则会出现一次数据更新时候的闪动,用户体验不好。(对于这个问题,当时的解决方案是增加清空数据的actions
,并且在页面组件的beforeDestroy
钩子函数中调用)
同时也增加了项目架构的复杂性,理解数据的流程更加困难,并且进行了很大一部分完全没有必要的编码。
针对这个问题,我早先和提供代码的人沟通过,他说是把数据缓存在 Vuex 中,并且把数据请求也封装进去,是为了方便埋点,做日志。精确记录用户都做了哪些数据请求,成功和失败的各种记录。
正确使用Vuex
配合 Vuejs Devtools,更容易理解Vuex。
先从语义上理解以下几个词:
- store 存储
- state 状态
- dispatch 调度
- action 行为
- mutation 转变
- commit 提交
使用规范
正确使用 Vuex,需要遵循这几个原则:
- 正确理解 Vuex 和 组件之间数据的单向流动
- 使用
dispatch
来调用定义好的action
,在组件中可以通过this.$store.dispatch
访问到。 - 使用
action
来提交mutation
,action
可以同时提交多个mutation
,并且可以包含异步操作来修改mutation
。 - 使用
mutation
来更新state
,mutation
只能同步地修改state
。 - 需要使用基于
state
而衍生的数据时候,可以定义一个getter
,类似于组件中的computed
。
Vue组织结构
Vuex 的组织是一个类似树状的结构,如下伪代码所示:
1 | export default new Vuex.Store({ |
辅助函数
命名空间模式 指,vuex 中的 某个module,设置了
namespaced: true
来导出。
mapState
在组件中使用可以导出状态数据。需要在computed字段使用。在状态树根部定义的属性可以通过
state.key
来进行访问,而在 module 中定义的的状态,则需要通过state.module.key
来实现访问。mapGetters
在组件中使用可以导出状态数据。需要在computed字段使用。在 getters 不多的情况下,建议只在 状态树 根部 定义 getters,并且抽离出单独的文件来进行维护。
mapActions
在组件中使用可以导出方法,需要在methods字段使用。普通的 module 和 状态树 根部定义的方法,都可以直接通过
mapActions
函数访问到。但是命名空间模式的方法导入,需要第一个参数传命名空间名,如...mapActions(NAMESPACE, ['foo'])
,或者通过...mapActions(['NAMESPACE/foo'])
的方式来进行导出。mapMutations
不建议做导出,因为官方推荐使用 action 来 commit 提交 mutations。并且不建议在组件中直接修改 state。
使用辅助函数导出数据,方法等有两种形式,一种保留本身的key
命名,一种根据需要进行自定义修改,前者语法简单,后者更为灵活。伪代码如下:
1 | import { mapState, mapGettters, maoActions, mapMutations } from 'vuex' |
状态切分
首先,需要明确的一点是,你也许并不需要 Vuex?
比如你没有或者只有很少的数据需要在组件间共享,那么也可以你可以采用 cookie,sessionStorage,localstorage或者 EventBus 等多种实现方式。
如果你是在构建一个大的应用,有多种状态需要管理,或者你需要考虑到系统后续的可扩展性,希望早期就使用这种更成熟的解决方案,那么 Vuex 将是你很好的选择。
那么,如何进行状态数据的切分呢?
- 首先我会考虑需要进行状态管理数据的复杂程度,几个数据并且不存在大的命名冲突的,就不使用模块化的方案,全部使用在根部的
state, actions, mutations, getters
。 - 如果页面属于UI上比较一致,组件的功能点上也不好做区分,需要在命名问题上下功夫的,那么建议将其切割成多个模块,保留语义性。或者页面的
actions, mutations
逻辑复杂,考虑到代码的可读性,建议使用模块化切分成多个文件,便于单独地去修改维护。
组件内使用
状态的使用,使用上述方法导入后,类似data
属性,都会被 vue
observe 到,同时被挂载在了组件实例上,通过this.foo
的方法即可使用。
方法的使用,使用mapActions
方法导入后,可以直接使用this
来进行调用。或者在组件中使用this.$store.dispatch('action_name', payload)
来进行调用。
Vuex数据流示例
总体,单项数据流程过程是,页面内的 dispatch
或者 action
调用,可传入需要的参数,再通过 commit
提交 mutation
,再在 mutation
中同步修改 state
,单向数据流动的 state
又更细到组件内,触发组件重新渲染。
1 | const state = { |