基于Vue设计中大型应用时,随着应用大小以及业务流程的膨胀,数据管理也必然成为其中的重要一环。数据管理主要包括数据的存取、刷新、传递等方面,其实这也是笔者一直很疑惑的一个点,包括在做Android应用时,数据管理应该是开发过程中很重要的一部分,网上却很少关于这方面的讨论或是框架(Vuex更多是提供数据共享功能,具体数据管理思想&实现封装还需自行把控),至少我没有发现,如果你知道什么更好的方法欢迎在评论中交流。
数据分类
根据数据的作用域,大致可以分为三类:
- 全局配置数据:与用户无关,应用的全局配置,这类数据可以考虑放在localStorage或是sessionStorage中。
- 用户数据:与登录用户绑定的数据,作用于用户登录后的所有流程,例如用户的基本信息、状态等。
- 流程数据:一个业务流程中的共享数据,具体又可细分为登录前和登录后流程数据。
根据数据的分类,主要需要注意离开作用域时以及重复进入作用域时数据的变化,简单来说,切换用户或者退出登录时,2、3两种数据应该被清空或者重置。
提供全局唯一的访问点
这也是Vuex框架的约定,state数据只能通过mutation来变更,我更推荐把这种约定作为数据访问的准则,不管是使用Vuex还是localStorage、sessionStorage,都应该封层一个全局数据访问入口,这样会极大地方便数据的统一管理和追踪。试想一下,如果从RESTful接口获取的数据需要做逻辑处理再保存时会怎么样,如果没有这样的全局唯一的访问入口,数据的获取和保存散落在各个页面中,势必增加很多额外的工作量。(当然Vuex本身支持这样的约定可以不用做额外的封装)
//封装用户数据的入口
export default{
getUserInfo(){
if(this.userInfo) return this.userInfo; //统一封装的入口可以很方便的提供一些通用的逻辑处理
this.userInfo = sessionStorage.getItem("userInfo");
this.userInfo && (this.userInfo = JSON.parse(this.userInfo));
return this.userInfo;
}
}
数据的存取&刷新
对于业务流程来说,只关心数据的获取结果,成功或者失败,所以一个功能完善的数据模块应该封装好数据的拉取、缓存、刷新,伪代码逻辑如下:
DataModule.getData().then(() => {
//数据获取成功,继续业务流程
}).catch(() => {
//数据获取失败,失败逻辑
})
当然,数据获取方式、刷新机制可能多种多样,但还是可以抽象出通用的特性,如果从接口设计角度来看,简单地可以抽象为fetch(数据拉取)、isNeedRefresh(是否需要刷新)两个接口,不同的数据Repository自行实现这两个接口,缓存和获取可以统一处理,然后由统一的DateCenter调用,伪代码如下(这边主要是提倡数据中心的设计思想,以Java的角度来看更易理解,前端童鞋可直接跳过):
//用户数据仓库
UserInfoRepository{
fetch(){
//userInfo的拉取逻辑
}
isNeedRefresh(userInfo){
//userInfo是否需要重新fetch的判断逻辑
}
}
//全局唯一的数据访问入口
DataCenter{
UserInfoRepository userInfoRepo;
getUserInfo(){
//这边只是伪代码示例,不考虑同步异步、userInfo的缓存细节
if(userInfoRepo.isNeedRefresh(this.userInfo)){
this.userInfo = userInfoRepo.fetch();
}
return this.userInfo;
}
}
//业务流程中调用就很简单,不需要考虑数据刷新、拉取等细节
DataCenter.getUserInfo()
当然,上述只是对问题的抽象,对应到Vuex框架的实现可以简单地使用action来完成UserInfoRepository的逻辑,实现代码如下:
//Vuex实现逻辑
export default{
state:{
userInfo:null
},
mutations:{
//全局唯一的数据修改点
saveUserInfo(state,userInfo){
state.userInfo = userInfo;
}
},
actions:{
getUserInfo(context,req){
if(context.state.userInfo && 无需更新判断逻辑){
return Promise.resolve(context.state.userInfo);
}else{
//对于数据的远程拉取需要自行封装统一的请求方法
return Axios.post(req).then(resp => {
context.state.commit("saveUserInfo",resp);
return Promise.resolve(context.state.userInfo);
})
}
}
}
//Vue控件中使用
this.$store.dispatch("getUserInfo",req).then(resp => {
//数据获取成功逻辑
}).catch(() => {
//错误提示
})
至此,对于外部业务系统来说,只需要关心数据获取之后的处理即可,每次获取到的也肯定是最新的数据。
如何确保数据非空
对于正常的流程来说,Vuex已经能够比较好的解决数据的存取问题,But,用户有意或者故意刷新了一下页面,你就会发现,诶,我数据呢,打开console一看,一堆的飘红!这也是Vuex的一个硬伤,页面刷新之后保存在内存中的数据全部都会丢失。对于这个问题的解决,笔者暂时未想到很完美的方案,这里提供两种方式做参考,如果你有更好的方案欢迎留言交流。
方式一:保存到sessionStorage或是localStorage
mutations:{
//全局唯一的数据修改点
saveUserInfo(state,userInfo){
state.userInfo = userInfo;
sessionStorage.setItem("userInfo",JSON.stringify(userInfo))
}
},
actions:{
getUserInfo(context,req){
if(!context.state.userInfo){
context.state.userInfo = sessionStorage.getItem("userInfo");
context.state.userInfo && (context.state.userInfo = JSON.parse(context.state.userInfo));
}
...同上
}
}
缺点:1、变化分散到sessionStorage/localStorage中,管理难度加大
2、sessionStorage/localStorage中的数据可能存在安全隐患,不推荐敏感数据的保存(目前在各大网站也未发现sessionStorage/localStorage中会保存很多数据)
方式二:结合Vue-Router&控件生命周期来确保数据的获取
对于全局的用户数据可在全局路由钩子中获取来确保
router.beforeEach((to, from, next) => {
if (如果是必需userInfo的页面) {
Store.dispatch("getUserInfo")
.then(() => {
next();
})
.catch(() => {
//跳转至错误页面
next({ name: "error" });
});
return;
}
next();
});
控件中利用beforeMount来实现
beforeMount() {
Store.dispatch("getUserInfo")
.then(() => {
//正常业务逻辑流程
})
.catch(() => {
//错误逻辑处理,这部分代码可以统一放到mixins或者Vuex中
});
}
缺点:1、每个页面需要考虑清楚各个数据的必要性
2、需要设计数据获取失败的交互逻辑,跳转错误页面等(这部分设计好问题倒不大)
业务流程中的数据传递
页面间的数据传递很简单,利用Vue-Router的params、query即可,问题是传递之后页面刷新的问题,页面刷新之后params数据会丢失,但是query数据由于会拼接在url中可以保留下来,所以对于采用何种方式传递数据以及页面刷新之后的处理,都需要根据业务流程具体分析:
对于query数据,适合传递简单数据,适用于数据的id、主键等,如交易记录的id,这样就可以在页面的生命周期钩子中根据query数据查出详细数据
beforeMount() {
Axios.post({recordId:this.$route.query.id}).then().catch();
}
而对于复杂业务数据的传递则需要使用params,但是页面刷新后params数据会丢失,这时可根据params是否存在判断是否跳出流程
beforeMount() {
if(this.$route.params.data){
//继续业务流程
}else{
this.$router.back();//or push到流程入口页面
}
}
跳出业务流程的处理方式主要有一个问题:对于嵌套流程,跳转到流程入口可能体验上不是很好。
对于流程中间的数据,个人不是很推荐保存到Vuex,除非你能确保管理好数据的生命周期,考虑到页面回退,用户重复进入流程,甚至极端情况下用户可能会手动输入网址跳转某个页面的情况,流程数据的缓存是有一定风险。当然,这不仅仅需要前端确保数据的准确性,还需要后端确保数据的校验拦截等。
数据的生命周期维护
对于Vuex中缓存的用户数据or流程数据,都需要在切换用户or退出流程时重置,提供重置数据的功能很简单,只需要在Vuex中增加clearState的mutation即可,难点是需要理清楚什么时候情况哪些数据需要重置,推荐将不同生命周期的数据封装成不同的module,然后提供不同的clear方法,在各个变更点调用即可。
clearState(state) {
//这边使用了lodash重置每个key,直接对state赋值是不起重要的
lodash.mapKeys(state, (val, key) => {
lodash.unset(state, key);
return key;
});
}
数据绑定问题
上面解决了数据的存取问题,当用到Vuex保存的数据做页面展示时可能会碰到数据还未获取成功的问题,这时console中一堆的undefine报错,甚至可能导致页面渲染失败,解决方法可采用computed的方式获取缓存数据中的字段:
<div>{{name}}</div>
computed:{
name(){
return this.$store.state.userInfo && this.$store.state.userInfo.person && this.$store.state.userInfo.person.name
}
}
结语
文中提出了数据管理过程中的一些问题,也给出了个人在实践过程中的解决思路,希望对大家有所帮助。当然,如果你有更好的方法,也欢迎留言交流。