大型Vuex應用程序的目錄結構
來自專欄 Fundebug
譯者按: 聽前端大佬聊聊Vuex大型項目架構的經驗
- 原文: Large-scale Vuex application structures
- 譯者: Fundebug
為了保證可讀性,本文採用意譯而非直譯。另外,本文版權歸原作者所有,翻譯僅用於學習。
在編寫大型應用程序時,管理前端的狀態可能會非常困難。 例如對於Vue.js應用程序,有一個名為Vuex的插件,它以非常簡單的方式提供狀態管理,並官方建議使用以下應用目錄結構:
對示例很感興趣,可以查看官方Vuex存儲庫(vuejs / vuex - shopping-cart)或者我創建的」購物車」示例(igeligel / vuex-simple-structure)。
官方推薦的目錄結構是非常不錯的,整個Vuex moudules看上去非常簡單並且包含了moudules作用域中的actions,getters和mutations。共享actions,getters或者mutations被直接保存到store目錄中。然後所有的modules,全局actions,getters和mutations被導入一個index.js
文件中,並在Vuex module的構造函數中再次導出。然而,當你moudules越來越多的時候就會出現問題,對於大型項目來說這是普遍的。想一下像GitLab這樣的大型項目,它包含非常非常多的modules。比如GitLab存儲庫視圖的側邊欄看起來是這個樣子的:
每個菜單條目基本上都是一個包含幾個actions,getter和getters和mutations的module。 所有這些都列在單個module文件中。 這不會很好地擴展,因為考慮到module需要的功能越來越多,module就會變的非常大甚至會超過1000行代碼。
但是這個問題有一個解決方案。 我們可以在module目錄中提取actions,getters和mutations。 全局actions,getters或mutations可以直接存儲在store目錄中。 應用程序結構如下所示:
基本上,你仍然有可能使用全局actions,getter和mutations,但我並不推薦它,因為它並不是真的有必要。 採用這種方法,我們將擁有多個獨立的文件。 聊天module的所有actions,getters和mutations將通過聊天目錄內的索引導入。 這個module將被導入到全局store。 需要注意的是,你應該在模塊內設置命名空間選項,以便你擁有適當的命名空間。 這是在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裡面,我們有兩個module:chat
和product
。 兩個模塊都包含actions,getters和mutations,並被導入到module的主module文件index.js
中,然後再次導出。 最後,導出的數據可以被store module使用。
這將註冊modules,並且代碼將以這樣的方式分離,並且它仍然是可讀的,可導航的和可維護的。 實現實例可查看bstavroulakis/vue-wordpress-pwa或者我自己實現的實例igeligel/vuex-namespaced-module-structure。 這個應用程序結構將很好地處理中小型應用程序。 代碼庫的新開發人員不會努力尋找業務邏輯所在的地方,因為每個模塊都在組件內部有適當的名稱和引用。 使用module真的很有趣,這在官方文檔中有解釋。
Fundebug錯誤實時監控為您的Vue項目保駕護航!
但是這樣也有一個問題。當你後端團隊創建越來越多的API時,並且程序變得越來越複雜,項目甚至達到20,30或者50個module。雖然維護沒有問題,但是新加入的實習生可能就會因為架構糾結了,因為他不確定業務邏輯的調用方式。然後你就會想如何更好去架構。你可以直接在組件中執行API調用,但是這造成巨大混亂,因為組件將會持有業務邏輯。組件應該只呈現數據,不處理數據
。
在React中有容器和組件的概念。 它沒有被Vue.js強制執行。 容器只是組件,但它們也可以從store 獲取數據並與store交互。 組件就在那裡保存數據並渲染它。 他們通過道具與上層集裝箱進行溝通。 讓我們想像一下我們應用程序中的聊天窗口小部件,它需要從商店獲取某種數據,或者從API獲得更好的數據。 我們將通過從聊天中獲取所有消息並且不提供實時支持來創建一個簡單示例。 讓我們假設我們有一些容器來保存整個聊天。 該容器將與商店通信以更新數據或將數據填充到表示組件。 整個架構在這個小圖中顯示:
在這個系統中,我們有一個名為Chat.vue
的容器,它與我們的store module chat
進行通信。 此chat module還通過調用API並更新store來處理邏輯。 當狀態最終更新容器時,Chat.vue
也將通過使用計算屬性進行更新,計算屬性將由Vue.js
和Vuex
的反應性更新。 之後,該屬性將作為props(傳值)傳遞給ChatList.vue
。 由於props(傳值)是該組件中的一個數組,所以會發生一次迭代,它將呈現一組ChatListElement.vue
組件,它們負責呈現聊天消息和元信息。
有了這種模式,我們已將應用程序分為三部分。 一部分是業務邏輯,它存在於store的module內,或者更普遍地存儲在store內,容器元素負責獲取數據並將其填充到呈現組件,這些組件僅用於呈現數據。 這為我們提供了很好的模塊化並支持單一責任原則。 它還提供了很好的可測試性,因為你可以自行測試此結構的每個部分。 他們一起將形成某種綜合測試。 但是這可以在另一篇文章中討論。
現在想像應用程序會增長很多。 很多的意思是指是你有幾個modules,不清楚這些modules在哪裡使用,哪些組件取決於它們,哪些不取決於它們。 在巨大的應用中,這可能是一個真正的問題。 想像一下,一個新的代碼庫可以忽略50個模塊和大約50個組件。 他會有一個很大的問題來導航。
Vuex推薦是在store目錄中有業務邏輯特徵的目錄。 有時與使用這些modules的容器的連接可能會斷開,並且不清楚使用哪些Vuex modules的位置。 有些modules可能只是在那裡,因為有一個容器,所以將這個業務邏輯放在處理數據的容器附近就好了。 讓我們稍微調整應用程序。 該模板基於vuejs-templates/webpack。
唯一的區別是我將Vuex安裝到這個模板中,設置它並在src目錄下面添加modules目錄。 稍後可以在此博客文章中找到此應用程序。 與此目錄的區別在於它包含modules。 不要將這些modules與Vuex modules混淆。 有可能是一個更好的名字,所以如果你知道一個名字,請在這篇文章下評論它。 因此,在modules目錄中,我們有這個Vue.js應用程序的模塊。 它看起來像這樣:
在modules目錄中,有幾個目錄描述不同的功能。例如,我們有chat和product功能。但有趣的是,它們在這些module目錄中,有一個store目錄,一個index.vue
文件和組件。清除一些干擾,我們只會看一下單個文件組件文件。 index.vue
被用作容器組件。此容器將從store中提取所有數據,並將這些數據作為props(傳值)傳遞給組件。組件ChatList.vue
和ChatListElement.vue
就是在那裡從組件中獲取數據並觸發到store的行為,該store全局連接到Vue.js實例。最大的問題是為什麼這些組件不在組件目錄中。原因是這些組件是專門為此功能而製作的。如果他們將被重新用於其他功能,那麼我會考慮將其移入組件目錄。基本上這裡的問題是,如果組件以某種方式被重用。然後我們應該將組件重構到共享組件目錄中。現在來看store。它與其他模式基本相同,但移入本地目錄store。要註冊它,我們使用Vuex的registerModule
函數。該功能將動態註冊Vuex模塊。通常它被用於插件,但我們將在這裡使用它來更好地分離問題。在index.vue文件
中,我們可以通過Vue.js
訪問生命周期函數,並且在創建的函數中,我們可以安全地創建module。
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); },};
我們用$ _作為前綴,表明這個module是私有的,因為它只在module中可用。 註冊後,該store將填充到我們的全局Vuex store。 從那裡開始,我們可以在組件內使用這些Vuex功能。 要註冊store,我們需要以某種方式將Vuex功能綁定到Vue.js實例。 這可以通過創建一個空的Vuex store,導出並將其附加到Vue.js構造函數來輕鬆完成。 查找這些文件以獲取想法(store/index.js,main.js)。
如果我們發現自己需要某種全局store,我會在store目錄下創建一個Vuex module,並使用推薦的結構。 例如,如果我們需要在應用程序內部的不同位置進行身份驗證,最好以不與容器耦合的方式進行共享。 這對於擁有共享的Vuex module將是一個很好的實例。
一些問題:可能不清楚哪些module需要全局或本地可用,而且很難做出決定。 它也很難找到應該是全局的組件,但基本上,所有通用組件應該位於由不同module使用的目錄中。 維持這種結構真的很難,但最終我認為為了擴展應用程序是值得的。 另一個問題是命名。 你現在有遍布整個地方的組件目錄。 將模塊_components
中的目錄命名為私有組件可能更好,但這是個人偏好。
這個結構的一個很好論點是modules在某種程度上是可以提取的。 如果一個功能變得太大,你可以通過在src/modules
目錄內的目錄中創建一個module來提取它,然後製作一個npm軟體包。 您需要導出的唯一東西是容器組件。 然後,這個npm包可以託管在公司的註冊表中,也可以公布在npm上。 只要確保以某種方式使Vuex模塊的行為可配置。 另一個很好的論點是測試可以用特徵範圍的方式編寫。
最積極的論點是Vuex module的範圍,容器和組件對於每個正在閱讀代碼的開發者都是清楚的。 由於在整個應用程序中使用關注點分離原則,因此可以快速找到每個功能的業務邏輯並且功能很容易測試。
不同結構的例子:
- igeligel/vuex-simple-structure
- igeligel/vuex-namespaced-module-structure
- igeligel/vuex-feature-scoped-structure
推薦閱讀: