我们是不是真的需要状态管理,答案是确定的,计算你的项目达到了10W行代码,那也并不意味着你必须使用它,应该由业务场景决定。引用Redux的作者Dan Abramov的话说就是:
Flux架构就像眼镜,您自会知道什么时候需要它。Vuex如是也。
Vuex争议
目前Vuex在网上争议并不大,用的好的人大有人在,不爱用的人也会使用其他解决方案来做。在《你可能不需要Vuex》一文中,已经对组件化通信讲的相当明确,也分析了Vuex作为集中式状态管理的好处。然而,大多数文章对具备落地性的Vuex使用场景并没有做整理和总结,都是一句“根据应用场景”来收尾。我个人是特别倾向使用Vuex的,他将分散在各处的数据进行集中式统一管理,视图层可以无侵入的接受可预测数据,有效降低耦合,颗粒化的维护各个模块。然而,在我们的实际开发过程中,仍然有一些开发者对Vuex存在质疑甚至恨之入骨,这很大一部分原因是由于个人的编程习惯和学习经验所致,另一方面也警示着架构需要考虑这些人的问题。
繁琐冗余
在一开始推广Vuex时,大部分开发人员学习完Vuex后都会向我反馈,说Vuex代码非常的繁琐且冗余,明明可以一行代码解决的,现在代码量和文件量都扩大了。这些开发人员大部分是经历过JQuery时代的悍将,他们早已习惯这种简单粗暴的开发方式,却很少阅读新的知识体系来让自己的技术有所升华。的确,最保守情况下,处理一个组件的数据,我们需要有一个对应的store
文件,里面包含了state
、getter
、mutation
、action
四个属性,state
定义可预测的数据,getter
提供自动计算的数据,mutation
用于单向改变属性,action
用于发送异步请求,原来一步可以到位的,现在要分成了四步,代码量增加了不说,分文件之后更带来了管理上麻烦。
内存占用
Vuex通过定义state
提供组件可预测的数据,当数据是由服务端获取的时候,交由action
来获取即可。然而,一部分人认为,对于列表页面,如果也使用store
,仅仅只是展示数据,除了之前的代码繁琐冗余之外,还会导致内存占用的问题。因为页面切换后,服务端获取的数据并没有被释放,仍然存储在state
中,即便从跳转的页面返回,仍然要重新从服务端获取一次最新数据。
综合解决方案
以上问题,要说是问题还真是个问题,前端架构中除了针对系统应用要有很好的战略布局,对人来说也要形成必要的约束和规范。针对这些,我们从前端架构中,增加了对Vuex的扩展,并制定相应的规则来规范开发人员的使用。
模块化
官网中有针对Vuex的模块化解决方案,这里我就不多赘述了,但官网提供了切割的方式,却没有给出很好的切割的解决方案。在我们的项目中,我们将开发的文件夹按照业务进行了划分,每个业务模块可以独立发布,也可以和其他的模块合并打包。项目的业务模块文件夹如下:
src
- modules ## 模块文件夹
- user ## 用户管理模块
- mock ## 数据模拟
- store ## 数据服务
- view ## 视图
- org ## 组织管理模块
- mock ## 数据模拟
- store ## 数据服务
- view ## 视图
- store-loader.js ## store加载器
所有视图的store模块
是由store-loader.js
加载出来的,它既是状态树的根
,同时又是module state
的加载器,负责将分散在各个模块的store
整合起来。因此,开发人员并不需要维护一个庞大的状态树,而只需要去维护自己的业务模块所需的模块数据即可。
按需动态加载
因为webpack工程让js可以提前预编译,所以,我们的架构中都是让store
和router
分散在各个模块中,编译时整合起来,并且这种整合我们是按需加载的。
我们的系统中是具备权限管理的,对于某些用户没有该模块的权限,那么这些js文件是不应该加载进来的,虽然会参与编译。在用户登陆之后,根据用户权限零时组装store
和router
。一方面让用户资源最小化,一方面也提升了应用性能和安全性。
我们的动态加载使用的是store
的registerModule
方法,同样的,对于一些特定路由的数据,在离开后可以使用unregisterModule
注销模块,删除数据,避免内存持续占用的问题。
内聚混合
看到以上这些模式,会有人有以下的疑问:
- 为什么没有router;
- 为什么没有api/services;
因为我们的项目架构把router
混合在了view
中,把api/services
混合进了store
中。前面的混合我会讲一个专题来解释为什么要这么做,后面的混合就是为了一部分解决之前我们提到的开发人员所遇到的痛点问题。我们可以来看如下案例:
import api from '@src/api/counter';
const store = {
state: {
count: 0
},
mutations: {
setCount (state, count) {
state.count = count;
}
},
actions: {
loadCount ({commit}) {
api.loadCount().then((count) = {
commit('setCount', count);
})
}
}
};
api
中通过axios
向服务端发起请求获取数据:
// @src/api/counter.js
import axios from 'axios';
export default {
loadCount: () => axios.get('/api/counter')
};
对于api/serives
,太薄了,仅仅只是对axios
进行了一个函数的包裹,而中间我们没有加入任何的逻辑,也不应该加入逻辑。这样做一方面不便于管理,也没办法发挥api
的意义。所以,我们将api
整合进了store
,对于仅仅用于展示的组件来说就更加便利了,不需要定义state
、mutation
,直接通过action
返回promise
,交由组件去使用。
总结
经过以上的思路,我们让代码尽可能的内聚集中,同时又分散耦合,配合服务端将加载量降低,通过架构中的模块让代码量也适当降低。当开发量变得巨大的时候,这种方式会降低很多度复杂度,让开发变得高效顺畅。