[譯]大型Vuex應(yīng)用結(jié)構(gòu)

原文:https://medium.com/3yourmind/large-scale-vuex-application-structures-651e44863e2
作者: @Kevin Peters

當(dāng)我試圖處理大型應(yīng)用程序中的狀態(tài)和結(jié)構(gòu)

在編寫大型應(yīng)用程序時蜕企,管理前端的狀態(tài)可能非常困難拓轻。例如娃弓,對于Vue.js應(yīng)用程序,有一個名為Vuex的插件赦肃,它以非常簡單的方式提供狀態(tài)管理,并建議使用以下應(yīng)用程序結(jié)構(gòu):

如果你對案例感興趣的話,可以查看官方Vuex案例中的購物車示例(vuejs/vuex—shopping-cart)或者我創(chuàng)建的示例(igeligel / vuex-simple-structure)森渐。

這真的很有效类腮,我們在這個模塊中擁有包含了action,getter和mutation的簡單的Vuex模塊臊泰。共享的action,getter和mutation直接保存在store目錄下。然后所有的組件蚜枢,全局action缸逃,getter和mutation被導(dǎo)入index.js文件,并在Vuex模塊的構(gòu)造函數(shù)中再次導(dǎo)出祟偷。然而當(dāng)有越來越多的組件的時候可能會出現(xiàn)問題察滑,對于大型應(yīng)用來說這是很常見的。想象一下像GitLab這樣的應(yīng)用程序修肠,它包含了很多的模塊贺辰。例如,GitLab的倉庫側(cè)邊欄看起來像這樣:

GitLab的倉庫側(cè)邊欄

每個菜單入口基本上都是一個包含了多個action,getter和mutation的組件嵌施。全部這些部分被羅列在一個單模塊文件中饲化。這并不能很好地?cái)U(kuò)展,因?yàn)榭紤]到模塊需要多少功能吗伤,甚至模塊都可能變得非常大吃靠,從而導(dǎo)致模塊擁有超過1000行代碼。
但是這個問題是有解決辦法的足淆。我們可以提取module目錄中的action巢块、getter和mutation。全局action巧号、getter或mutation可以直接存在于store目錄中族奢。應(yīng)用程序結(jié)構(gòu)如下:

基于模塊的存儲

基本上,你仍然有可能使用全局action丹鸿、getter和mutation,但是我建議你這么做越走,因?yàn)樗皇钦嬲匦璧摹J褂眠@種方法,我們將會有多個分離的文件廊敌。 chat模塊中所有的action铜跑、getter和mutatioin將會由chat模塊中的索引導(dǎo)入。 然后骡澈,此模塊將導(dǎo)入到全局存儲中锅纺。需要注意的是你應(yīng)該在module中設(shè)置命名空間選項(xiàng),以便具有正確的命名空間秧廉。 這在store / index.js文件中完成:

import Vue from 'vue';
import Vuex from 'vuex';
import chatModule from './modules/chat/index';
import productsModule from './modules/products/index';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    chat: chatModule,
    products: productsModule,
  },
});

在store中,我們擁有chat和products兩個模塊伞广,它們兩都擁有action、getter和mutations疼电,而且都被導(dǎo)入到主模塊文件index.js然后再次導(dǎo)出嚼锄。最后導(dǎo)出數(shù)據(jù)可以被store模塊使用。

模塊結(jié)構(gòu)

這將注冊模塊蔽豺,然后代碼將會以一種可讀区丑、可導(dǎo)航、可維護(hù)的方式分離修陡。關(guān)于這種執(zhí)行方法的例子可以在bstavroulakis/vue-wordpress-pwa或者在我的示例igeligel/vuex-namespaced-module-structure中找到沧侥。這種應(yīng)用程序結(jié)構(gòu)可以很好的處理中小型應(yīng)用。代碼庫的新開發(fā)人員不會很難找到業(yè)務(wù)邏輯所在的位置魄鸦,因?yàn)槊總€模塊在組件內(nèi)部都擁有一個合理的名稱和引用宴杀。使用模塊真的很有趣,這在官方文檔中有解釋拾因。

不過在某些時刻旺罢,存在一個問題。當(dāng)你的后端團(tuán)隊(duì)添加更多的API绢记,這個應(yīng)用程序變得越來越復(fù)雜扁达。當(dāng)你擁有20,30甚至50個模塊的時候蠢熄,雖然仍然是可維護(hù)的跪解,但是你的新開發(fā)人員會覺得很費(fèi)勁,因?yàn)樗淮_定業(yè)務(wù)邏輯在哪里被調(diào)用签孔。然后你會思考如何更好的的構(gòu)建叉讥。你可能會直接在組件中調(diào)用API,但是這將會造成一個巨大的混亂饥追,因?yàn)榻M件將持有業(yè)務(wù)邏輯图仓。組件應(yīng)該渲染數(shù)據(jù)而不是處理數(shù)據(jù)。

在React中有容器和組件的概念判耕。Vue.js并沒有強(qiáng)力執(zhí)行透绩。容器也是組件翘骂,他們都能從store獲取數(shù)據(jù)并進(jìn)行繪畫壁熄。組件只是用來保存數(shù)據(jù)和渲染數(shù)據(jù)帚豪。他們通過props和上層容器進(jìn)行通信。讓我們設(shè)想一下草丧。應(yīng)用程序中的一個聊天小部件狸臣,它需要從存儲中獲取某種數(shù)據(jù),甚至從API中獲得更好的數(shù)據(jù)昌执。我們將創(chuàng)建一個簡單的示例烛亦,從聊天中獲取所有消息,而不提供實(shí)時支持懂拾。讓我們假設(shè)我們擁有某種容器能夠保存整個chat煤禽。這個容器將會和store進(jìn)行通信以更新數(shù)據(jù),或者將數(shù)據(jù)填充到展示組件岖赋。這整個架構(gòu)顯示在以下的小圖中檬果。

聊天工具的架構(gòu)

在這個系統(tǒng)中,我們擁有一個叫做Chat.vue的容器唐断,它和store模塊chat通信选脊。這個chat模塊調(diào)用API和更新store處理邏輯。當(dāng)state最終更新容器時脸甘,Chat.vue也會通過計(jì)算屬性進(jìn)行更新恳啥,該屬性將根據(jù)Vue.js和Vuex的反應(yīng)進(jìn)行更新。在此之后丹诀,該屬性將作為props傳遞給ChatList.vue钝的。因?yàn)檫@個組件中的props是個數(shù)組,因此將進(jìn)行一個迭代以渲染ChatListElement中的一個數(shù)組忿墅。Vue組件負(fù)責(zé)渲染聊天消息和元信息扁藕。

通過這種模式,我們把應(yīng)用程序分成三個部分疚脐。一部分是存在于store模塊中的業(yè)務(wù)邏輯亿柑,或者更一般地說是存在于store中。容器元素負(fù)責(zé)獲取到數(shù)據(jù)并將數(shù)據(jù)填充到展示組件棍弄,展示組件只是用于渲染數(shù)據(jù)望薄。這為我們提供了很好的模塊化,并支持單一責(zé)任原則呼畸。它還提供了良好的可測試性痕支,因?yàn)槟梢宰约簻y試這個結(jié)構(gòu)的每個部分。它們一起會形成某種集成測試蛮原。但這可以在另一篇文章中討論卧须。

現(xiàn)在假設(shè)應(yīng)用程序變大了很多,我的意思是你有很多模塊,但是不清楚這些模塊在哪里使用花嘶,哪些組件依賴它們笋籽,哪些不依賴它們。在大型應(yīng)用程序中椭员,這可能是一個真正的問題车海。想象一下,一個剛接觸這個代碼的人可以忽略50個模塊和大約50個組件隘击。他會有一個大問題要解決侍芝。

Vuex的建議是在store目錄中包含業(yè)務(wù)邏輯特性的目錄。有時埋同,與使用這些模塊的容器的連接可能會被破壞州叠,而使用這些Vuex模塊的地方就不清楚了。有些模塊可能只是因?yàn)橐粋€容器而存在凶赁,所以最好將這個業(yè)務(wù)邏輯放在容器附近留量,以便處理數(shù)據(jù)。讓我們對應(yīng)用程序進(jìn)行一些重構(gòu)哟冬。這個模板基于vuejb -templates/webpack楼熄。

改進(jìn)之后的應(yīng)用程序結(jié)構(gòu)

唯一的區(qū)別是,我將Vuex安裝到這個模板中浩峡,設(shè)置它并在src目錄下添加modules目錄可岂。您可以在本文后面的文章中找到這個應(yīng)用程序。這個目錄的不同之處在于它包含模塊翰灾。不要將這些模塊與Vuex模塊混合缕粹。可能有一個更好的名字纸淮,所以如果你知道平斩,請?jiān)谶@篇文章下評論。在modules目錄中咽块,我們有這個Vue的模塊绘面。js應(yīng)用程序。它看起來是這樣的:

模塊目錄內(nèi)部

在modules目錄中侈沪,有幾個描述不同功能的目錄揭璃。例如,我們有聊天和產(chǎn)品功能亭罪。但有趣的是瘦馍,在那些modules目錄中。我們有一個store目錄应役,一個index.vue文件和組件情组。為了清楚起見燥筷,我們將只查看單個文件組件文件。index.vue用作容器組件院崇。此容器將從store中提取所有數(shù)據(jù)荆责,并將此數(shù)據(jù)作為props傳遞給組件。組件ChatList.vue和ChatListElement.vue就是從組件中獲取數(shù)據(jù)并觸發(fā)對存儲的操作亚脆,該存儲全局附加到Vue.js實(shí)例。最大的問題是為什么這些組件不在組件目錄中盲泛。原因是這些組件是專門為此功能而制作的濒持。如果它們已被重用于另一個功能,那么我會考慮將其移動到組件目錄中寺滚「逃基本上這里的問題是,組件是否以某種方式重用村视。然后我們應(yīng)該將組件重構(gòu)到共享組件目錄中」偬祝現(xiàn)在來說store。它與其他模式基本相同蚁孔,但移入本地目錄存儲奶赔。要注冊它,我們使用Vuex的registerModule函數(shù)杠氢。該函數(shù)將動態(tài)注冊Vuex模塊站刑。通常它用于插件,但我們會在這里使用它來更好地分離關(guān)注點(diǎn)鼻百。在index.vue文件中绞旅,我們可以通過Vue.js訪問生命周期函數(shù),在創(chuàng)建的函數(shù)內(nèi)部温艇,我們可以安全地創(chuàng)建模塊因悲。

import { mapGetters } from 'vuex';
import store from './_store';
import ChatList from './_components/ChatList';

export default {
  name: 'ChatModule',
  components: {
    ChatList,
  },
  computed: {
    ...mapGetters({
      messages: '$_chat/messages',
    }),
  },
  created() {
    this.$store.registerModule('$_chat', store);
  },
  mounted() {
    this.$store.dispatch('$_chat/getMessages');
  },
};

我們在前面加上$ _來表示該模塊是私有的,因?yàn)樗辉谀K中可用勺爱。注冊之后晃琳,store將被填充到全局Vuex store中。之后我們便可以在組件內(nèi)部使用這些Vuex函數(shù)琐鲁。要注冊store蝎土,我們需要某種方式把Vuex功能添加到Vue.js實(shí)例中。這可以通過創(chuàng)建空的Vuex store绣否,導(dǎo)出它并將其附加到Vue.js構(gòu)造函數(shù)來輕松完成誊涯。可以查看這些文件store/index.js, main.js獲得靈感蒜撮。

如果我們需要某個全局store,我會使用推薦的結(jié)構(gòu)創(chuàng)建一個在store目錄下的Vuex組件暴构。比如我們需要在應(yīng)用程序中的不同地方進(jìn)行身份校驗(yàn)跪呈,那么最好以不與容器耦合的方式共享它。這是一個使用共享Vuex組件的很好的例子取逾。

全局Vuex組件

其中的一些缺陷:可能不清楚哪些模塊是全局的耗绿,哪些模塊是局部的,這真的很難決定砾隅。也很難找到全局組件误阻,但是基本上,所有通用組件都應(yīng)該在這個目錄中晴埂,不同的模塊使用這個目錄究反。維護(hù)這個結(jié)構(gòu)確實(shí)很困難,但是最后儒洛,我認(rèn)為為了擴(kuò)展應(yīng)用程序精耐,它是值得的。另一個陷阱是命名±哦停現(xiàn)在到處都有組件目錄卦停。在模塊_components中命名目錄可能更好,以顯示它們是私有組件恼蓬,但這是個人偏好惊完。

這種結(jié)構(gòu)的一個很好的論據(jù)是模塊在某種程度上是可提取的。 如果某個功能太大处硬,你可以通過在src / modules目錄下的目錄中創(chuàng)建一個模塊來提取它专执,然后從中創(chuàng)建一個npm包。 唯一需要導(dǎo)出的是容器組件郁油。 然后本股,這個npm包可以在您公司的注冊表中托管,也可以在npm上公開托管桐腌。 只需確保以某種方式使Vuex模塊的行為可配置拄显。 另一個好的論點(diǎn)是測試可以用特征范圍的方式編寫。

最好的結(jié)果是案站,每個閱讀代碼的開發(fā)人員都很清楚Vuex模塊躬审、容器和組件。你可以很快找到每一個功能的業(yè)務(wù)邏輯蟆盐,并且功能很容易測試承边,因?yàn)樵谡麄€應(yīng)用程序中使用了關(guān)注點(diǎn)分離的原則。

不同結(jié)構(gòu)的例子:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末石挂,一起剝皮案震驚了整個濱河市博助,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌痹愚,老刑警劉巖富岳,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛔糯,死亡現(xiàn)場離奇詭異,居然都是意外死亡窖式,警方通過查閱死者的電腦和手機(jī)蚁飒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萝喘,“玉大人淮逻,你說我怎么就攤上這事「篝ぃ” “怎么了爬早?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長强窖。 經(jīng)常有香客問我,道長削祈,這世上最難降的妖魔是什么翅溺? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮髓抑,結(jié)果婚禮上咙崎,老公的妹妹穿的比我還像新娘。我一直安慰自己吨拍,他們只是感情好褪猛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著羹饰,像睡著了一般伊滋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上队秩,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天笑旺,我揣著相機(jī)與錄音,去河邊找鬼馍资。 笑死筒主,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸟蟹。 我是一名探鬼主播乌妙,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼建钥!你這毒婦竟也來了藤韵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤熊经,失蹤者是張志新(化名)和其女友劉穎荠察,沒想到半個月后置蜀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悉盆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年盯荤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焕盟。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡秋秤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脚翘,到底是詐尸還是另有隱情灼卢,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布来农,位于F島的核電站鞋真,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沃于。R本人自食惡果不足惜涩咖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望繁莹。 院中可真熱鬧檩互,春花似錦、人聲如沸咨演。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽薄风。三九已至饵较,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間遭赂,已是汗流浹背告抄。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嵌牺,地道東北人打洼。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像逆粹,于是被迫代替她去往敵國和親募疮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

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