Vue:基于Vuex的數(shù)據(jù)管理

基于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ù)的作用域,大致可以分為三類:

  1. 全局配置數(shù)據(jù):與用戶無關(guān),應(yīng)用的全局配置,這類數(shù)據(jù)可以考慮放在localStorage或是sessionStorage中。
  2. 用戶數(shù)據(jù):與登錄用戶綁定的數(shù)據(jù),作用于用戶登錄后的所有流程,例如用戶的基本信息、狀態(tài)等。
  3. 流程數(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)然,如果你有更好的方法,也歡迎留言交流。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 32,311評論 2 89
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,673評論 1 32
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標(biāo)準(zhǔn)。 注意:講述HT...
    kismetajun閱讀 28,826評論 1 45
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,419評論 4 61
  • 實(shí)戰(zhàn)腳手架準(zhǔn)備分三部曲 Flask實(shí)戰(zhàn)腳手架 (后端+restful api)Yii2實(shí)戰(zhàn)腳手架 (后端+rest...
    七霸刀閱讀 2,259評論 0 7

友情鏈接更多精彩內(nèi)容