如何管理 vue 項目中的數(shù)據(jù)?

vuex

如何管理 vue 項目的數(shù)據(jù)炭菌?這個問題似乎早已經(jīng)有答案了黑低,無非就是使用 vuex ,全局 store蕾管,整個應(yīng)用維護(hù)一個超大的 Object掰曾,界面的顯示情況隨著超大 Object 的變化而變化停团。

看起來很簡單,不就維護(hù)一個 Object 嘛秒梅,實際上捆蜀,要想組織好數(shù)據(jù)這塊代碼辆琅,必須事先對項目的數(shù)據(jù)結(jié)構(gòu)理解得非常透徹婉烟,然后像設(shè)計數(shù)據(jù)庫表一樣把各個 module 的樣子設(shè)計出來似袁。實際上咐刨,個人覺得設(shè)計 vuex 的 module 比設(shè)計數(shù)據(jù)庫表復(fù)雜得多:

  • 1定鸟、像數(shù)據(jù)庫一樣設(shè)計各個業(yè)務(wù)實體的外貌,這部分設(shè)計難度應(yīng)該和數(shù)據(jù)庫表設(shè)計差不多啼县;
  • 2、維護(hù)一堆 ajax 請求狀態(tài)余蟹;
  • 3子刮、如何優(yōu)雅地復(fù)用 module挺峡。比如有一個 PersonListModule,在一個頁面上有兩處要用到 PersonListModule 中的列表數(shù)據(jù):一個是要在表格控件里面展示佛呻,一個是要在下拉控件里面展示吓著,每個控件中展示的列表數(shù)據(jù)篩選條件不一樣送挑;
  • 4、如何同步 vuex 中的數(shù)據(jù)和服務(wù)器端數(shù)據(jù)纺裁。vuex 的超大 Object 可以看做服務(wù)器端數(shù)據(jù)在客戶端內(nèi)存中的一個緩存欺缘,怎么設(shè)計這個緩存的同步策略挤安?

對于3蛤铜、4兩個問題,結(jié)合起來更恐怖:同步服務(wù)器端數(shù)據(jù)到 PersonListModule 的同時剿干,還要考慮如何從 PersonListModule 中篩選出分頁數(shù)據(jù)到頁面展示置尔,還要篩選出多個列表氢伟,還要考慮在什么時機(jī)重新更新“緩存”幽歼,想想就頭大试躏。

假設(shè)我們能力很強(qiáng)大颠蕴,設(shè)計出了能完美應(yīng)對上述問題的 store 方案助析,還有一個大問題攔著我們呢:如何保證這套設(shè)計的可擴(kuò)展性外冀?因為業(yè)務(wù)系統(tǒng)變化多端,不知道什么時候產(chǎn)品經(jīng)理又有新想法了西轩,我們得設(shè)計能很好地應(yīng)對變化多端的需求嗎藕畔?

為什么這么難庄拇?問題究竟出現(xiàn)在哪里?

vuex 的思維模式主要是從數(shù)據(jù)著手溶弟,由數(shù)據(jù)推導(dǎo)出界面的樣子辜御,這就需要先設(shè)計好 store 結(jié)構(gòu)了凰浮。要設(shè)計好 store 結(jié)構(gòu),目測必須具備如下特質(zhì)的工程師才能做好:

  • 1、對項目業(yè)務(wù)了解非常深入笛厦;
  • 2俺夕、具備超強(qiáng)的抽象思維能力;
  • 3姨谷、經(jīng)驗豐富,能盡量想到設(shè)計出的 store 結(jié)構(gòu)能應(yīng)付哪些情況瞎颗、不能應(yīng)付哪些情況哼拔。

第2條的門檻實在是太高了瓣颅,能做到的前端工程師估計沒多少宫补。

怎么辦?

我們不應(yīng)該從數(shù)據(jù)推導(dǎo)出界面健民,而應(yīng)該從界面推導(dǎo)出數(shù)據(jù)荞雏,逐層抽象平酿。

比如現(xiàn)在要仿一個新浪微博首頁,頁面上主要包含的數(shù)據(jù)有:分組信息筑辨、微博列表棍辕、個人信息还绘、一些推薦信息等,那么就設(shè)計一個只針對該頁面的 module 抚太,大致結(jié)構(gòu)為:

const homePageModule = {
  state: {
    groupList: [{
        id: 1,
        name: '名人明星',
        unread: 1
      },
      {
        id: 2,
        name: '同事',
        unread: 0
      }
    ],
    groupListExtraInfo: {
      // 初始顯示多少個小組
      initShowCount: 5,
      loading: true
    },
    weiboList: [{
      id: 1,
      content: '<p>震驚部</p>',
      author: 'yibuyisheng',
      createTime: '20170719234422'
    }],
    weiboListPageInfo: {
      loadingStatus: 'QUITE', // 三種取值:QUITE -> 沒有加載电媳;UP -> 向上加載庆亡;DOWN -> 向下加載
      // weiboList 的開始時間,可用這個時間戳做下一次的向上加載
      startTime: '20170719234422',
      // weiboList 的結(jié)束時間拼缝,可用這個時間戳做下一次的向下加載
      endTime: '20170719234422'
    },
    self: {
      id: 1,
      nickname: 'yibuyisheng',
      email: 'yibuyisheng@163.com',
      avatar: 'http://weibo.com/2674779523/profile?rightmod=1&wvr=6&mod=personinfo',
      followedCount: 405,
      followerCount: 235,
      weiboCount: 1321
    },
    recommendMovies: [
      ...
    ],
    recommendTopics: [
      ...
    ]
    ...
  },
  mutations: {
    updateWeiboList(state, list) {
      ...
    }
  },
  actions: {
    appendWeiboList() {
      ...
    },
    prependWeiboList() {
      ...
    }
  }
};

針對這個頁面珍促,這個結(jié)構(gòu)猪叙,各個處理邏輯就具體化仁卷、特殊化了,代碼寫起來非常輕松芒帕。

代碼復(fù)用背蟆?

假設(shè)現(xiàn)在有個小組頁面哮幢,點進(jìn)去后可以看到該小組所有成員發(fā)的微博,因為是一個新的頁面垛叨,所以需要新起一個 module 嗽元,這也意味著要重復(fù)寫一遍 weiboList 相關(guān)的代碼喂击,豈不蛋疼翰绊!

此時可以考慮寫一個 createWeiboListModule() 函數(shù),用于創(chuàng)建這種通用 module 琳要,然后再寫一個 mergeModules() 函數(shù)稚补,把 createWeiboListModule() 函數(shù)創(chuàng)建出來的 module 對象和各頁面特殊的 module 合并起來框喳,樣子看起來大致是這樣:

mergeModules(createWeiboListModule(), {
  state: {
    ...
  },
  mutations: {
    ...
  },
  actions: {
    ...
  }
});

遇到需要復(fù)用的才抽取通用邏輯,很自然乍惊,很簡單润绎。

怎么結(jié)合 vue 組件诞挨?

上面的結(jié)構(gòu)有一個很大的問題,就是不能很好地和 vue 組件結(jié)合棍郎。比如涂佃,要讓微博首頁和分組頁面中的微博列表能復(fù)用 weiboList 相關(guān)代碼蜈敢,那么 weiboList 涉及到的 state扶认、action、mutation狱从、getter 的命名都要盡量保持一致叠纹,不然就要傳一個 nameMap(命名映射)給兩個頁面通用的 WeiboListComponent 組件誉察,看起來就像這樣:

<weibo-list-component :name-map="{weiboList: 'homePageWeiboList'}"></weibo-list-component>

簡直蛋疼!

好吧驼卖,那就嚴(yán)格約束這兩個頁面的 state、action怎囚、mutation恳守、getter 命名都保持一致吧贩虾!

簡直超級蛋疼!

此時可以考慮用 namespace 來解決這個問題伊群,比如上面的 homePageModule 可以把 weiboList 拆分出來:

const store = new vuex.Store({
  ...,

  modules: {
    'page:home': {
      state: {
        groupList: [{
            id: 1,
            name: '名人明星',
            unread: 1
          },
          {
            id: 2,
            name: '同事',
            unread: 0
          }
        ],
        groupListExtraInfo: {
          // 初始顯示多少個小組
          initShowCount: 5,
          loading: true
        },
        self: {
          id: 1,
          nickname: 'yibuyisheng',
          email: 'yibuyisheng@163.com',
          avatar: 'http://weibo.com/2674779523/profile?rightmod=1&wvr=6&mod=personinfo',
          followedCount: 405,
          followerCount: 235,
          weiboCount: 1321
        },
        recommendMovies: [
          ...
        ],
        recommendTopics: [
            ...
          ]
          ...
      },
    },
    'page:home:weiboList': createWeiboListModule(...)
  }

  ...
});

這樣一來在岂,只要給 vue 組件傳一個 namespace 參數(shù)就行了:

<weibo-list-component namespace="page:home:weiboList"></weibo-list-component>

嗯蔽午,看起來挺好的及老!

如何處理“store 緩存”范抓?

可以在上一個問題解決的基礎(chǔ)上匕垫,加上緩存功能,目測有大把現(xiàn)成的緩存策略可以參考(服務(wù)器端都玩兒爛了)寞秃,由于絕大部分系統(tǒng)并不需要這層緩存功能偶惠,所以此處不贅述。

就這樣了嗎绑改?

上述方案,思維方向的確是導(dǎo)致最后執(zhí)行起來輕松了很多识腿,從具體到抽象的過程皆的,很自然,符合思考習(xí)慣。但是最終的代碼還是會很容易搞得很亂的:

  • 1楞抡、mergeModules() 要照顧各種合并策略析藕;
  • 2、createXXXModule() 方法會抽出很多層竞慢。比如可以從 createWeiboListModule() 抽出來 createContinuousListModule() 筹煮,用于構(gòu)造通用的具備“向前向后”加載能力的列表 Module居夹,最終可能會形成一條常常的“繼承鏈”准脂,需要自己去定義維護(hù)這套繼承邏輯,心累沟饥。

其實上面兩條一看湾戳,就知道有現(xiàn)成的解決方案了: class。

參考此處實現(xiàn):https://github.com/yibuyisheng/vuex-model/blob/master/src/store/BaseModule.js (代碼還在完善中)遮晚。

具體業(yè)務(wù)代碼寫起來就像是這樣了:

class ContinuousList extends BaseModule {
    state = {
        list: [],
        pageInfo: {
            loadingStatus: 'QUITE',
            startTime: '20170720003939',
            endTime: '20170720003939'
        }
    }
    
    @action
    async appendList(...) { 
        ...
        const result = await request('some url', params);
        this.updateList(result.list);
        ...
    }
    
    @action
    prependList(...) { ... }
}

class WeiboList extends ContinuousList {
    
    @action
    async voteUp(...) {
        ...
        await request('some url', params);
        const weiboDetail = await updateWeibo('some url', params.weiboId);
        const newList = this.state.list.map((wb) => {
            return wb.id === weiboDetail.id ? weiboDetail : wb;
        });
        this.updateList(newList);
        ...
    }
}

@composition(WeiboList)
class HomePage extends BaseModule {

    $namespace = 'page:home:';
    
    ...
    @action
    requestRecommendInfo(...) {
        ...
    }
    ...
}

HomePage.register();

在對應(yīng)的 HomePage.vue 里面县遣,大致是這樣:

<template>
    <div class="home-page-view">
        ...
        <weibo-list-component namespace="page:home:weiboList"></weibo-list-component>
        ...
    </div>
</template>
<script>
export default {
    created() {
        ...
        const constants = this.getConstants('page:home');
        this.$store.dispatch(constants.REQUEST_RECOMMEND_INFO, params);
        ...
    }
};
</script>

WeiboListComponent 組件大致是這樣:

<template>
    <div class="weibo-list-component">
        ...
    </div>
</template>
<script>
export default {
    props: {
        namespace: {
            type: String,
            required: true
        }
    },
    computed: {
        weiboList() {
            const constants = this.getConstants(this.namespace);
            return this.$store.getters[constants.LIST];
        }
    },
    created() {
        ...
        const constants = this.getConstants(this.namespace);
        this.$store.dispatch(constants.APPEND_LIST, params);
        ...
    }
};
</script>

總結(jié)

其實就是換一種思路:從界面推導(dǎo)數(shù)據(jù)其兴,從具體到抽象夸政。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末守问,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子穆端,更是在濱河造成了極大的恐慌体啰,老刑警劉巖嗽仪,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闻坚,死亡現(xiàn)場離奇詭異,居然都是意外死亡搀擂,警方通過查閱死者的電腦和手機(jī)卷玉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門相种,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人箫措,你說我怎么就攤上這事衬潦《频海” “怎么了友驮?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵卸留,是天一觀的道長耻瑟。 經(jīng)常有香客問我赏酥,道長,這世上最難降的妖魔是什么算柳? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮蔗蹋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘餐塘。我一直安慰自己皂吮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布需纳。 她就那樣靜靜地躺著不翩,像睡著了一般麻裳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妙蔗,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天眉反,我揣著相機(jī)與錄音,去河邊找鬼禁漓。 笑死,一個胖子當(dāng)著我的面吹牛伶跷,可吹牛的內(nèi)容都是我干的叭莫。 我是一名探鬼主播烁试,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼靖诗!你這毒婦竟也來了刊橘?” 一聲冷哼從身側(cè)響起颂鸿,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎败晴,沒想到半個月后栽渴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡糖驴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年贮缕,在試婚紗的時候發(fā)現(xiàn)自己被綠了感昼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罐脊。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖宵溅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雏搂,我是刑警寧澤寇损,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布矛市,位于F島的核電站,受9級特大地震影響浊吏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜配紫,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望享扔。 院中可真熱鬧,春花似錦籽懦、人聲如沸氛魁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惫恼。三九已至,卻和暖如春祈纯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背腕窥。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工簇爆, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拦惋。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓厕妖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親言秸。 傳聞我的和親對象是個殘疾皇子迎捺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 我這個人有時候很有憤青的潛質(zhì)叛买,我能夠很輕易地說出讓你沮喪的話蹋订。毫不費力,甚至很愿意費勁口舌的打擊人椒功。 有些時候我老...
    簡西云閱讀 409評論 0 0
  • 你好智什,想了許久我也不知道怎么開口稱呼你,那就姑且叫你吧谦炬! 其實在之前不懂事的時候,我希望你呀最好是完美無暇的键思,但是...
    陸陸陸離閱讀 309評論 0 0
  • 因為面試需要,準(zhǔn)備突擊一下這本書吼鳞。說實話有點作死的感覺,畢竟多線程一直是本人的大傷之一供炎。速讀一下疾党,先為這周的面試做...
    來往穿梭閱讀 199評論 0 0
  • 今天星期五,我放學(xué)后幫伯伯擦鏡框竭钝,在做之前我還打了乒乓球雹洗。開始我先把布弄濕了,剛開始的時候覺得鏡框很臟时肿,擦的時候很...
    烏克麗麗滿閱讀 399評論 1 0
  • 武老師的這個實體實驗讓我覺得很可怕螃成,雖然沒真正去做,但是在空白的大腦想過這個嬰兒是個如何的畫面顷霹,個人感覺還是挺好的...
    城市格調(diào)劉姣閱讀 363評論 0 0