原文:https://medium.com/3yourmind/large-scale-vuex-application-structures-651e44863e2
作者: @Kevin Peters
在編寫大型應(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è)邊欄看起來像這樣:
每個菜單入口基本上都是一個包含了多個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模塊使用。
這將注冊模塊蔽豺,然后代碼將會以一種可讀区丑、可導(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)顯示在以下的小圖中檬果。
在這個系統(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楼熄。
唯一的區(qū)別是,我將Vuex安裝到這個模板中浩峡,設(shè)置它并在src目錄下添加modules目錄可岂。您可以在本文后面的文章中找到這個應(yīng)用程序。這個目錄的不同之處在于它包含模塊翰灾。不要將這些模塊與Vuex模塊混合缕粹。可能有一個更好的名字纸淮,所以如果你知道平斩,請?jiān)谶@篇文章下評論。在modules目錄中咽块,我們有這個Vue的模塊绘面。js應(yīng)用程序。它看起來是這樣的:
在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組件的很好的例子取逾。
其中的一些缺陷:可能不清楚哪些模塊是全局的耗绿,哪些模塊是局部的,這真的很難決定砾隅。也很難找到全局組件误阻,但是基本上,所有通用組件都應(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)的例子: