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

在編寫(xiě)大型應(yīng)用程序時(shí),管理前端的狀態(tài)可能非常困難。例如,對(duì)于Vue.js應(yīng)用程序,有一個(gè)名為Vuex的插件,它以非常簡(jiǎn)單的方式提供狀態(tài)管理,并建議使用以下應(yīng)用程序結(jié)構(gòu):
如果你對(duì)案例感興趣的話,可以查看官方Vuex案例中的購(gòu)物車(chē)示例(vuejs/vuex—shopping-cart)或者我創(chuàng)建的示例(igeligel / vuex-simple-structure)。
這真的很有效,我們?cè)谶@個(gè)模塊中擁有包含了action,getter和mutation的簡(jiǎn)單的Vuex模塊。共享的action,getter和mutation直接保存在store目錄下。然后所有的組件,全局action,getter和mutation被導(dǎo)入index.js文件,并在Vuex模塊的構(gòu)造函數(shù)中再次導(dǎo)出。然而當(dāng)有越來(lái)越多的組件的時(shí)候可能會(huì)出現(xiàn)問(wèn)題,對(duì)于大型應(yīng)用來(lái)說(shuō)這是很常見(jiàn)的。想象一下像GitLab這樣的應(yīng)用程序,它包含了很多的模塊。例如,GitLab的倉(cāng)庫(kù)側(cè)邊欄看起來(lái)像這樣:

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

基本上,你仍然有可能使用全局action、getter和mutation,但是我建議你這么做,因?yàn)樗皇钦嬲匦璧?。使用這種方法,我們將會(huì)有多個(gè)分離的文件。 chat模塊中所有的action、getter和mutatioin將會(huì)由chat模塊中的索引導(dǎo)入。 然后,此模塊將導(dǎo)入到全局存儲(chǔ)中。需要注意的是你應(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兩個(gè)模塊,它們兩都擁有action、getter和mutations,而且都被導(dǎo)入到主模塊文件index.js然后再次導(dǎo)出。最后導(dǎo)出數(shù)據(jù)可以被store模塊使用。

這將注冊(cè)模塊,然后代碼將會(huì)以一種可讀、可導(dǎo)航、可維護(hù)的方式分離。關(guān)于這種執(zhí)行方法的例子可以在bstavroulakis/vue-wordpress-pwa或者在我的示例igeligel/vuex-namespaced-module-structure中找到。這種應(yīng)用程序結(jié)構(gòu)可以很好的處理中小型應(yīng)用。代碼庫(kù)的新開(kāi)發(fā)人員不會(huì)很難找到業(yè)務(wù)邏輯所在的位置,因?yàn)槊總€(gè)模塊在組件內(nèi)部都擁有一個(gè)合理的名稱(chēng)和引用。使用模塊真的很有趣,這在官方文檔中有解釋。
不過(guò)在某些時(shí)刻,存在一個(gè)問(wèn)題。當(dāng)你的后端團(tuán)隊(duì)添加更多的API,這個(gè)應(yīng)用程序變得越來(lái)越復(fù)雜。當(dāng)你擁有20,30甚至50個(gè)模塊的時(shí)候,雖然仍然是可維護(hù)的,但是你的新開(kāi)發(fā)人員會(huì)覺(jué)得很費(fèi)勁,因?yàn)樗淮_定業(yè)務(wù)邏輯在哪里被調(diào)用。然后你會(huì)思考如何更好的的構(gòu)建。你可能會(huì)直接在組件中調(diào)用API,但是這將會(huì)造成一個(gè)巨大的混亂,因?yàn)榻M件將持有業(yè)務(wù)邏輯。組件應(yīng)該渲染數(shù)據(jù)而不是處理數(shù)據(jù)。
在React中有容器和組件的概念。Vue.js并沒(méi)有強(qiáng)力執(zhí)行。容器也是組件,他們都能從store獲取數(shù)據(jù)并進(jìn)行繪畫(huà)。組件只是用來(lái)保存數(shù)據(jù)和渲染數(shù)據(jù)。他們通過(guò)props和上層容器進(jìn)行通信。讓我們?cè)O(shè)想一下。應(yīng)用程序中的一個(gè)聊天小部件,它需要從存儲(chǔ)中獲取某種數(shù)據(jù),甚至從API中獲得更好的數(shù)據(jù)。我們將創(chuàng)建一個(gè)簡(jiǎn)單的示例,從聊天中獲取所有消息,而不提供實(shí)時(shí)支持。讓我們假設(shè)我們擁有某種容器能夠保存整個(gè)chat。這個(gè)容器將會(huì)和store進(jìn)行通信以更新數(shù)據(jù),或者將數(shù)據(jù)填充到展示組件。這整個(gè)架構(gòu)顯示在以下的小圖中。

在這個(gè)系統(tǒng)中,我們擁有一個(gè)叫做Chat.vue的容器,它和store模塊chat通信。這個(gè)chat模塊調(diào)用API和更新store處理邏輯。當(dāng)state最終更新容器時(shí),Chat.vue也會(huì)通過(guò)計(jì)算屬性進(jìn)行更新,該屬性將根據(jù)Vue.js和Vuex的反應(yīng)進(jìn)行更新。在此之后,該屬性將作為props傳遞給ChatList.vue。因?yàn)檫@個(gè)組件中的props是個(gè)數(shù)組,因此將進(jìn)行一個(gè)迭代以渲染ChatListElement中的一個(gè)數(shù)組。Vue組件負(fù)責(zé)渲染聊天消息和元信息。
通過(guò)這種模式,我們把應(yīng)用程序分成三個(gè)部分。一部分是存在于store模塊中的業(yè)務(wù)邏輯,或者更一般地說(shuō)是存在于store中。容器元素負(fù)責(zé)獲取到數(shù)據(jù)并將數(shù)據(jù)填充到展示組件,展示組件只是用于渲染數(shù)據(jù)。這為我們提供了很好的模塊化,并支持單一責(zé)任原則。它還提供了良好的可測(cè)試性,因?yàn)槟梢宰约簻y(cè)試這個(gè)結(jié)構(gòu)的每個(gè)部分。它們一起會(huì)形成某種集成測(cè)試。但這可以在另一篇文章中討論。
現(xiàn)在假設(shè)應(yīng)用程序變大了很多,我的意思是你有很多模塊,但是不清楚這些模塊在哪里使用,哪些組件依賴(lài)它們,哪些不依賴(lài)它們。在大型應(yīng)用程序中,這可能是一個(gè)真正的問(wèn)題。想象一下,一個(gè)剛接觸這個(gè)代碼的人可以忽略50個(gè)模塊和大約50個(gè)組件。他會(huì)有一個(gè)大問(wèn)題要解決。
Vuex的建議是在store目錄中包含業(yè)務(wù)邏輯特性的目錄。有時(shí),與使用這些模塊的容器的連接可能會(huì)被破壞,而使用這些Vuex模塊的地方就不清楚了。有些模塊可能只是因?yàn)橐粋€(gè)容器而存在,所以最好將這個(gè)業(yè)務(wù)邏輯放在容器附近,以便處理數(shù)據(jù)。讓我們對(duì)應(yīng)用程序進(jìn)行一些重構(gòu)。這個(gè)模板基于vuejb -templates/webpack。

唯一的區(qū)別是,我將Vuex安裝到這個(gè)模板中,設(shè)置它并在src目錄下添加modules目錄。您可以在本文后面的文章中找到這個(gè)應(yīng)用程序。這個(gè)目錄的不同之處在于它包含模塊。不要將這些模塊與Vuex模塊混合??赡苡幸粋€(gè)更好的名字,所以如果你知道,請(qǐng)?jiān)谶@篇文章下評(píng)論。在modules目錄中,我們有這個(gè)Vue的模塊。js應(yīng)用程序。它看起來(lái)是這樣的:

在modules目錄中,有幾個(gè)描述不同功能的目錄。例如,我們有聊天和產(chǎn)品功能。但有趣的是,在那些modules目錄中。我們有一個(gè)store目錄,一個(gè)index.vue文件和組件。為了清楚起見(jiàn),我們將只查看單個(gè)文件組件文件。index.vue用作容器組件。此容器將從store中提取所有數(shù)據(jù),并將此數(shù)據(jù)作為props傳遞給組件。組件ChatList.vue和ChatListElement.vue就是從組件中獲取數(shù)據(jù)并觸發(fā)對(duì)存儲(chǔ)的操作,該存儲(chǔ)全局附加到Vue.js實(shí)例。最大的問(wèn)題是為什么這些組件不在組件目錄中。原因是這些組件是專(zhuān)門(mén)為此功能而制作的。如果它們已被重用于另一個(gè)功能,那么我會(huì)考慮將其移動(dòng)到組件目錄中。基本上這里的問(wèn)題是,組件是否以某種方式重用。然后我們應(yīng)該將組件重構(gòu)到共享組件目錄中。現(xiàn)在來(lái)說(shuō)store。它與其他模式基本相同,但移入本地目錄存儲(chǔ)。要注冊(cè)它,我們使用Vuex的registerModule函數(shù)。該函數(shù)將動(dòng)態(tài)注冊(cè)Vuex模塊。通常它用于插件,但我們會(huì)在這里使用它來(lái)更好地分離關(guān)注點(diǎn)。在index.vue文件中,我們可以通過(guò)Vue.js訪問(wèn)生命周期函數(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');
},
};
我們?cè)谇懊婕由? _來(lái)表示該模塊是私有的,因?yàn)樗辉谀K中可用。注冊(cè)之后,store將被填充到全局Vuex store中。之后我們便可以在組件內(nèi)部使用這些Vuex函數(shù)。要注冊(cè)store,我們需要某種方式把Vuex功能添加到Vue.js實(shí)例中。這可以通過(guò)創(chuàng)建空的Vuex store,導(dǎo)出它并將其附加到Vue.js構(gòu)造函數(shù)來(lái)輕松完成??梢圆榭催@些文件store/index.js, main.js獲得靈感。
如果我們需要某個(gè)全局store,我會(huì)使用推薦的結(jié)構(gòu)創(chuàng)建一個(gè)在store目錄下的Vuex組件。比如我們需要在應(yīng)用程序中的不同地方進(jìn)行身份校驗(yàn),那么最好以不與容器耦合的方式共享它。這是一個(gè)使用共享Vuex組件的很好的例子。

其中的一些缺陷:可能不清楚哪些模塊是全局的,哪些模塊是局部的,這真的很難決定。也很難找到全局組件,但是基本上,所有通用組件都應(yīng)該在這個(gè)目錄中,不同的模塊使用這個(gè)目錄。維護(hù)這個(gè)結(jié)構(gòu)確實(shí)很困難,但是最后,我認(rèn)為為了擴(kuò)展應(yīng)用程序,它是值得的。另一個(gè)陷阱是命名?,F(xiàn)在到處都有組件目錄。在模塊_components中命名目錄可能更好,以顯示它們是私有組件,但這是個(gè)人偏好。
這種結(jié)構(gòu)的一個(gè)很好的論據(jù)是模塊在某種程度上是可提取的。 如果某個(gè)功能太大,你可以通過(guò)在src / modules目錄下的目錄中創(chuàng)建一個(gè)模塊來(lái)提取它,然后從中創(chuàng)建一個(gè)npm包。 唯一需要導(dǎo)出的是容器組件。 然后,這個(gè)npm包可以在您公司的注冊(cè)表中托管,也可以在npm上公開(kāi)托管。 只需確保以某種方式使Vuex模塊的行為可配置。 另一個(gè)好的論點(diǎn)是測(cè)試可以用特征范圍的方式編寫(xiě)。
最好的結(jié)果是,每個(gè)閱讀代碼的開(kāi)發(fā)人員都很清楚Vuex模塊、容器和組件。你可以很快找到每一個(gè)功能的業(yè)務(wù)邏輯,并且功能很容易測(cè)試,因?yàn)樵谡麄€(gè)應(yīng)用程序中使用了關(guān)注點(diǎn)分離的原則。
不同結(jié)構(gòu)的例子:
