基于Vue設(shè)計中大型應(yīng)用時,隨著應(yīng)用大小以及業(yè)務(wù)流程的膨脹,數(shù)據(jù)管理也必然成為其中的重要一環(huán)。數(shù)據(jù)管理主要包括數(shù)據(jù)的存取、刷新、傳遞等方面,其實(shí)這也是筆者一直很疑惑的一個點(diǎn),包括在做Android應(yīng)用時,數(shù)據(jù)管理應(yīng)該是開發(fā)過程中很重要的一部分,網(wǎng)上卻很少關(guān)于這方面的討論或是框架(Vuex更多是提供數(shù)據(jù)共享功能,具體數(shù)據(jù)管理思想&實(shí)現(xiàn)封裝還需自行把控),至少我沒有發(fā)現(xiàn),如果你知道什么更好的方法歡迎在評論中交流。
數(shù)據(jù)分類
根據(jù)數(shù)據(jù)的作用域,大致可以分為三類:
- 全局配置數(shù)據(jù):與用戶無關(guān),應(yīng)用的全局配置,這類數(shù)據(jù)可以考慮放在localStorage或是sessionStorage中。
- 用戶數(shù)據(jù):與登錄用戶綁定的數(shù)據(jù),作用于用戶登錄后的所有流程,例如用戶的基本信息、狀態(tài)等。
- 流程數(shù)據(jù):一個業(yè)務(wù)流程中的共享數(shù)據(jù),具體又可細(xì)分為登錄前和登錄后流程數(shù)據(jù)。
根據(jù)數(shù)據(jù)的分類,主要需要注意離開作用域時以及重復(fù)進(jìn)入作用域時數(shù)據(jù)的變化,簡單來說,切換用戶或者退出登錄時,2、3兩種數(shù)據(jù)應(yīng)該被清空或者重置。
提供全局唯一的訪問點(diǎn)
這也是Vuex框架的約定,state數(shù)據(jù)只能通過mutation來變更,我更推薦把這種約定作為數(shù)據(jù)訪問的準(zhǔn)則,不管是使用Vuex還是localStorage、sessionStorage,都應(yīng)該封層一個全局?jǐn)?shù)據(jù)訪問入口,這樣會極大地方便數(shù)據(jù)的統(tǒng)一管理和追蹤。試想一下,如果從RESTful接口獲取的數(shù)據(jù)需要做邏輯處理再保存時會怎么樣,如果沒有這樣的全局唯一的訪問入口,數(shù)據(jù)的獲取和保存散落在各個頁面中,勢必增加很多額外的工作量。(當(dāng)然Vuex本身支持這樣的約定可以不用做額外的封裝)
//封裝用戶數(shù)據(jù)的入口
export default{
getUserInfo(){
if(this.userInfo) return this.userInfo; //統(tǒng)一封裝的入口可以很方便的提供一些通用的邏輯處理
this.userInfo = sessionStorage.getItem("userInfo");
this.userInfo && (this.userInfo = JSON.parse(this.userInfo));
return this.userInfo;
}
}
數(shù)據(jù)的存取&刷新
對于業(yè)務(wù)流程來說,只關(guān)心數(shù)據(jù)的獲取結(jié)果,成功或者失敗,所以一個功能完善的數(shù)據(jù)模塊應(yīng)該封裝好數(shù)據(jù)的拉取、緩存、刷新,偽代碼邏輯如下:
DataModule.getData().then(() => {
//數(shù)據(jù)獲取成功,繼續(xù)業(yè)務(wù)流程
}).catch(() => {
//數(shù)據(jù)獲取失敗,失敗邏輯
})
當(dāng)然,數(shù)據(jù)獲取方式、刷新機(jī)制可能多種多樣,但還是可以抽象出通用的特性,如果從接口設(shè)計角度來看,簡單地可以抽象為fetch(數(shù)據(jù)拉?。?、isNeedRefresh(是否需要刷新)兩個接口,不同的數(shù)據(jù)Repository自行實(shí)現(xiàn)這兩個接口,緩存和獲取可以統(tǒng)一處理,然后由統(tǒng)一的DateCenter調(diào)用,偽代碼如下(這邊主要是提倡數(shù)據(jù)中心的設(shè)計思想,以Java的角度來看更易理解,前端童鞋可直接跳過):
//用戶數(shù)據(jù)倉庫
UserInfoRepository{
fetch(){
//userInfo的拉取邏輯
}
isNeedRefresh(userInfo){
//userInfo是否需要重新fetch的判斷邏輯
}
}
//全局唯一的數(shù)據(jù)訪問入口
DataCenter{
UserInfoRepository userInfoRepo;
getUserInfo(){
//這邊只是偽代碼示例,不考慮同步異步、userInfo的緩存細(xì)節(jié)
if(userInfoRepo.isNeedRefresh(this.userInfo)){
this.userInfo = userInfoRepo.fetch();
}
return this.userInfo;
}
}
//業(yè)務(wù)流程中調(diào)用就很簡單,不需要考慮數(shù)據(jù)刷新、拉取等細(xì)節(jié)
DataCenter.getUserInfo()
當(dāng)然,上述只是對問題的抽象,對應(yīng)到Vuex框架的實(shí)現(xiàn)可以簡單地使用action來完成UserInfoRepository的邏輯,實(shí)現(xiàn)代碼如下:
//Vuex實(shí)現(xiàn)邏輯
export default{
state:{
userInfo:null
},
mutations:{
//全局唯一的數(shù)據(jù)修改點(diǎn)
saveUserInfo(state,userInfo){
state.userInfo = userInfo;
}
},
actions:{
getUserInfo(context,req){
if(context.state.userInfo && 無需更新判斷邏輯){
return Promise.resolve(context.state.userInfo);
}else{
//對于數(shù)據(jù)的遠(yuǎn)程拉取需要自行封裝統(tǒng)一的請求方法
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 => {
//數(shù)據(jù)獲取成功邏輯
}).catch(() => {
//錯誤提示
})
至此,對于外部業(yè)務(wù)系統(tǒng)來說,只需要關(guān)心數(shù)據(jù)獲取之后的處理即可,每次獲取到的也肯定是最新的數(shù)據(jù)。
如何確保數(shù)據(jù)非空
對于正常的流程來說,Vuex已經(jīng)能夠比較好的解決數(shù)據(jù)的存取問題,But,用戶有意或者故意刷新了一下頁面,你就會發(fā)現(xiàn),誒,我數(shù)據(jù)呢,打開console一看,一堆的飄紅!這也是Vuex的一個硬傷,頁面刷新之后保存在內(nèi)存中的數(shù)據(jù)全部都會丟失。對于這個問題的解決,筆者暫時未想到很完美的方案,這里提供兩種方式做參考,如果你有更好的方案歡迎留言交流。
方式一:保存到sessionStorage或是localStorage
mutations:{
//全局唯一的數(shù)據(jù)修改點(diǎn)
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));
}
...同上
}
}
缺點(diǎn):1、變化分散到sessionStorage/localStorage中,管理難度加大
2、sessionStorage/localStorage中的數(shù)據(jù)可能存在安全隱患,不推薦敏感數(shù)據(jù)的保存(目前在各大網(wǎng)站也未發(fā)現(xiàn)sessionStorage/localStorage中會保存很多數(shù)據(jù))
方式二:結(jié)合Vue-Router&控件生命周期來確保數(shù)據(jù)的獲取
對于全局的用戶數(shù)據(jù)可在全局路由鉤子中獲取來確保
router.beforeEach((to, from, next) => {
if (如果是必需userInfo的頁面) {
Store.dispatch("getUserInfo")
.then(() => {
next();
})
.catch(() => {
//跳轉(zhuǎn)至錯誤頁面
next({ name: "error" });
});
return;
}
next();
});
控件中利用beforeMount來實(shí)現(xiàn)
beforeMount() {
Store.dispatch("getUserInfo")
.then(() => {
//正常業(yè)務(wù)邏輯流程
})
.catch(() => {
//錯誤邏輯處理,這部分代碼可以統(tǒng)一放到mixins或者Vuex中
});
}
缺點(diǎn):1、每個頁面需要考慮清楚各個數(shù)據(jù)的必要性
2、需要設(shè)計數(shù)據(jù)獲取失敗的交互邏輯,跳轉(zhuǎn)錯誤頁面等(這部分設(shè)計好問題倒不大)
業(yè)務(wù)流程中的數(shù)據(jù)傳遞
頁面間的數(shù)據(jù)傳遞很簡單,利用Vue-Router的params、query即可,問題是傳遞之后頁面刷新的問題,頁面刷新之后params數(shù)據(jù)會丟失,但是query數(shù)據(jù)由于會拼接在url中可以保留下來,所以對于采用何種方式傳遞數(shù)據(jù)以及頁面刷新之后的處理,都需要根據(jù)業(yè)務(wù)流程具體分析:
對于query數(shù)據(jù),適合傳遞簡單數(shù)據(jù),適用于數(shù)據(jù)的id、主鍵等,如交易記錄的id,這樣就可以在頁面的生命周期鉤子中根據(jù)query數(shù)據(jù)查出詳細(xì)數(shù)據(jù)
beforeMount() {
Axios.post({recordId:this.$route.query.id}).then().catch();
}
而對于復(fù)雜業(yè)務(wù)數(shù)據(jù)的傳遞則需要使用params,但是頁面刷新后params數(shù)據(jù)會丟失,這時可根據(jù)params是否存在判斷是否跳出流程
beforeMount() {
if(this.$route.params.data){
//繼續(xù)業(yè)務(wù)流程
}else{
this.$router.back();//or push到流程入口頁面
}
}
跳出業(yè)務(wù)流程的處理方式主要有一個問題:對于嵌套流程,跳轉(zhuǎn)到流程入口可能體驗(yàn)上不是很好。
對于流程中間的數(shù)據(jù),個人不是很推薦保存到Vuex,除非你能確保管理好數(shù)據(jù)的生命周期,考慮到頁面回退,用戶重復(fù)進(jìn)入流程,甚至極端情況下用戶可能會手動輸入網(wǎng)址跳轉(zhuǎn)某個頁面的情況,流程數(shù)據(jù)的緩存是有一定風(fēng)險。當(dāng)然,這不僅僅需要前端確保數(shù)據(jù)的準(zhǔn)確性,還需要后端確保數(shù)據(jù)的校驗(yàn)攔截等。
數(shù)據(jù)的生命周期維護(hù)
對于Vuex中緩存的用戶數(shù)據(jù)or流程數(shù)據(jù),都需要在切換用戶or退出流程時重置,提供重置數(shù)據(jù)的功能很簡單,只需要在Vuex中增加clearState的mutation即可,難點(diǎn)是需要理清楚什么時候情況哪些數(shù)據(jù)需要重置,推薦將不同生命周期的數(shù)據(jù)封裝成不同的module,然后提供不同的clear方法,在各個變更點(diǎn)調(diào)用即可。
clearState(state) {
//這邊使用了lodash重置每個key,直接對state賦值是不起重要的
lodash.mapKeys(state, (val, key) => {
lodash.unset(state, key);
return key;
});
}
數(shù)據(jù)綁定問題
上面解決了數(shù)據(jù)的存取問題,當(dāng)用到Vuex保存的數(shù)據(jù)做頁面展示時可能會碰到數(shù)據(jù)還未獲取成功的問題,這時console中一堆的undefine報錯,甚至可能導(dǎo)致頁面渲染失敗,解決方法可采用computed的方式獲取緩存數(shù)據(jù)中的字段:
<div>{{name}}</div>
computed:{
name(){
return this.$store.state.userInfo && this.$store.state.userInfo.person && this.$store.state.userInfo.person.name
}
}
結(jié)語
文中提出了數(shù)據(jù)管理過程中的一些問題,也給出了個人在實(shí)踐過程中的解決思路,希望對大家有所幫助。當(dāng)然,如果你有更好的方法,也歡迎留言交流。