[译]大型Vuex应用结构

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

当我试图处理大型应用程序中的状态和结构

在编写大型应用程序时,管理前端的状态可能非常困难。例如,对于Vue.js应用程序,有一个名为Vuex的插件,它以非常简单的方式提供状态管理,并建议使用以下应用程序结构:

如果你对案例感兴趣的话,可以查看官方Vuex案例中的购物车示例(vuejs/vuex—shopping-cart)或者我创建的示例(igeligel / vuex-simple-structure)

这真的很有效,我们在这个模块中拥有包含了action,getter和mutation的简单的Vuex模块。共享的action,getter和mutation直接保存在store目录下。然后所有的组件,全局action,getter和mutation被导入index.js文件,并在Vuex模块的构造函数中再次导出。然而当有越来越多的组件的时候可能会出现问题,对于大型应用来说这是很常见的。想象一下像GitLab这样的应用程序,它包含了很多的模块。例如,GitLab的仓库侧边栏看起来像这样:

GitLab的仓库侧边栏

每个菜单入口基本上都是一个包含了多个action,getter和mutation的组件。全部这些部分被罗列在一个单模块文件中。这并不能很好地扩展,因为考虑到模块需要多少功能,甚至模块都可能变得非常大,从而导致模块拥有超过1000行代码。
但是这个问题是有解决办法的。我们可以提取module目录中的action、getter和mutation。全局action、getter或mutation可以直接存在于store目录中。应用程序结构如下:

基于模块的存储

基本上,你仍然有可能使用全局action、getter和mutation,但是我建议你这么做,因为它不是真正必需的。使用这种方法,我们将会有多个分离的文件。 chat模块中所有的action、getter和mutatioin将会由chat模块中的索引导入。 然后,此模块将导入到全局存储中。需要注意的是你应该在module中设置命名空间选项,以便具有正确的命名空间。 这在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,而且都被导入到主模块文件index.js然后再次导出。最后导出数据可以被store模块使用。

模块结构

这将注册模块,然后代码将会以一种可读、可导航、可维护的方式分离。关于这种执行方法的例子可以在bstavroulakis/vue-wordpress-pwa或者在我的示例igeligel/vuex-namespaced-module-structure中找到。这种应用程序结构可以很好的处理中小型应用。代码库的新开发人员不会很难找到业务逻辑所在的位置,因为每个模块在组件内部都拥有一个合理的名称和引用。使用模块真的很有趣,这在官方文档中有解释。

不过在某些时刻,存在一个问题。当你的后端团队添加更多的API,这个应用程序变得越来越复杂。当你拥有20,30甚至50个模块的时候,虽然仍然是可维护的,但是你的新开发人员会觉得很费劲,因为他不确定业务逻辑在哪里被调用。然后你会思考如何更好的的构建。你可能会直接在组件中调用API,但是这将会造成一个巨大的混乱,因为组件将持有业务逻辑。组件应该渲染数据而不是处理数据。

在React中有容器和组件的概念。Vue.js并没有强力执行。容器也是组件,他们都能从store获取数据并进行绘画。组件只是用来保存数据和渲染数据。他们通过props和上层容器进行通信。让我们设想一下。应用程序中的一个聊天小部件,它需要从存储中获取某种数据,甚至从API中获得更好的数据。我们将创建一个简单的示例,从聊天中获取所有消息,而不提供实时支持。让我们假设我们拥有某种容器能够保存整个chat。这个容器将会和store进行通信以更新数据,或者将数据填充到展示组件。这整个架构显示在以下的小图中。

聊天工具的架构

在这个系统中,我们拥有一个叫做Chat.vue的容器,它和store模块chat通信。这个chat模块调用API和更新store处理逻辑。当state最终更新容器时,Chat.vue也会通过计算属性进行更新,该属性将根据Vue.js和Vuex的反应进行更新。在此之后,该属性将作为props传递给ChatList.vue。因为这个组件中的props是个数组,因此将进行一个迭代以渲染ChatListElement中的一个数组。Vue组件负责渲染聊天消息和元信息。

通过这种模式,我们把应用程序分成三个部分。一部分是存在于store模块中的业务逻辑,或者更一般地说是存在于store中。容器元素负责获取到数据并将数据填充到展示组件,展示组件只是用于渲染数据。这为我们提供了很好的模块化,并支持单一责任原则。它还提供了良好的可测试性,因为您可以自己测试这个结构的每个部分。它们一起会形成某种集成测试。但这可以在另一篇文章中讨论。

现在假设应用程序变大了很多,我的意思是你有很多模块,但是不清楚这些模块在哪里使用,哪些组件依赖它们,哪些不依赖它们。在大型应用程序中,这可能是一个真正的问题。想象一下,一个刚接触这个代码的人可以忽略50个模块和大约50个组件。他会有一个大问题要解决。

Vuex的建议是在store目录中包含业务逻辑特性的目录。有时,与使用这些模块的容器的连接可能会被破坏,而使用这些Vuex模块的地方就不清楚了。有些模块可能只是因为一个容器而存在,所以最好将这个业务逻辑放在容器附近,以便处理数据。让我们对应用程序进行一些重构。这个模板基于vuejb -templates/webpack

改进之后的应用程序结构

唯一的区别是,我将Vuex安装到这个模板中,设置它并在src目录下添加modules目录。您可以在本文后面的文章中找到这个应用程序。这个目录的不同之处在于它包含模块。不要将这些模块与Vuex模块混合。可能有一个更好的名字,所以如果你知道,请在这篇文章下评论。在modules目录中,我们有这个Vue的模块。js应用程序。它看起来是这样的:

模块目录内部

在modules目录中,有几个描述不同功能的目录。例如,我们有聊天和产品功能。但有趣的是,在那些modules目录中。我们有一个store目录,一个index.vue文件和组件。为了清楚起见,我们将只查看单个文件组件文件。index.vue用作容器组件。此容器将从store中提取所有数据,并将此数据作为props传递给组件。组件ChatList.vue和ChatListElement.vue就是从组件中获取数据并触发对存储的操作,该存储全局附加到Vue.js实例。最大的问题是为什么这些组件不在组件目录中。原因是这些组件是专门为此功能而制作的。如果它们已被重用于另一个功能,那么我会考虑将其移动到组件目录中。基本上这里的问题是,组件是否以某种方式重用。然后我们应该将组件重构到共享组件目录中。现在来说store。它与其他模式基本相同,但移入本地目录存储。要注册它,我们使用Vuex的registerModule函数。该函数将动态注册Vuex模块。通常它用于插件,但我们会在这里使用它来更好地分离关注点。在index.vue文件中,我们可以通过Vue.js访问生命周期函数,在创建的函数内部,我们可以安全地创建模块。

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');
  },
};

我们在前面加上$ _来表示该模块是私有的,因为它只在模块中可用。注册之后,store将被填充到全局Vuex store中。之后我们便可以在组件内部使用这些Vuex函数。要注册store,我们需要某种方式把Vuex功能添加到Vue.js实例中。这可以通过创建空的Vuex store,导出它并将其附加到Vue.js构造函数来轻松完成。可以查看这些文件store/index.js, main.js获得灵感。

如果我们需要某个全局store,我会使用推荐的结构创建一个在store目录下的Vuex组件。比如我们需要在应用程序中的不同地方进行身份校验,那么最好以不与容器耦合的方式共享它。这是一个使用共享Vuex组件的很好的例子。

全局Vuex组件

其中的一些缺陷:可能不清楚哪些模块是全局的,哪些模块是局部的,这真的很难决定。也很难找到全局组件,但是基本上,所有通用组件都应该在这个目录中,不同的模块使用这个目录。维护这个结构确实很困难,但是最后,我认为为了扩展应用程序,它是值得的。另一个陷阱是命名。现在到处都有组件目录。在模块_components中命名目录可能更好,以显示它们是私有组件,但这是个人偏好。

这种结构的一个很好的论据是模块在某种程度上是可提取的。 如果某个功能太大,你可以通过在src / modules目录下的目录中创建一个模块来提取它,然后从中创建一个npm包。 唯一需要导出的是容器组件。 然后,这个npm包可以在您公司的注册表中托管,也可以在npm上公开托管。 只需确保以某种方式使Vuex模块的行为可配置。 另一个好的论点是测试可以用特征范围的方式编写。

最好的结果是,每个阅读代码的开发人员都很清楚Vuex模块、容器和组件。你可以很快找到每一个功能的业务逻辑,并且功能很容易测试,因为在整个应用程序中使用了关注点分离的原则。

不同结构的例子:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容