总结——使用组件的典型原因:
- 可维护性。
- 通过组件边界呈现性能。
- 通过分块加载性能。
如果您发现使用更少的组件可以提高可维护性,那很好。它可能是适合您的应用程序的正确设计。
详细版本如下。
使用组件的主要原因是为了提高可维护性。
在许多地方(例如选择框)重复使用的组件显然比一遍又一遍地重复相同的代码更容易维护。但是,值得考虑为什么会这样。不仅仅是重复使更改所有选择框变得更加困难。使用组件的另一个主要好处是额外的抽象级别可以减少心理开销。
假设有人试图维护代码看到这样的东西(伪代码):
<select-box :some-prop="blah">
<select-box-option v-for="something" />
</select-box>
立即很明显这是select-box。 select-box 的所有血腥实现细节都被隐藏起来了,我们不需要担心它们。通过查看道具、子组件和事件,我们可以快速推断出父组件和select-box 之间来回传递的数据。这只是关注点分离。
此好处也适用于未重用的组件。让我们以侧边栏为例。我们可能会在主要组件的模板中看到这一点:
<nav-sidebar @navigate="onNavigate" />
组件的名称可以让我们快速将其识别为侧边栏。如果我们当前的任务不涉及侧边栏,那么我们可以跳过模板的那一部分。由于代码已移至不同的文件,我们可以轻松确定哪些代码位是侧边栏的一部分,哪些位不是。
在这个例子中,nav-sidebar 没有任何道具,只有一个事件。由此我们可以开始得出关于这些组件如何交互的一些结论。 nav-sidebar 似乎不需要从主要组件传递任何东西,它可以很高兴地独立生活。如果我们需要调试数据以其他方式流动的问题,我们几乎肯定会从 onNavigate 开始。
如果所有东西都被拼凑成一个大的组成部分,我们就无法像这样快速地开始进行推理。
当然,我们的推论可能是错误的。可能是nav-sidebar 做了一些可怕的事情,涉及$parent 从其父组件中获取数据。然而,这只是说明了为什么使用这些技术被认为是不好的做法。可维护的代码应该允许开发人员根据似乎已经存在的抽象得出合理的结论。
但也有可能走得太远。
一个好的抽象可以让你通过隐藏标签后面的细节来释放一些心理能力。一个糟糕的抽象通过隐藏你想在一些间接后面看到的代码增加了精神开销。再加上命名的困难和额外的胶水代码的负担,你最好放弃额外的层并保持一切内联。
另一件可能出错的事情是以错误的方式拆分组件。分离关注点需要对这些关注点进行干净的分区。以稍微不同的方式进行拆分,您最终会发现一个问题被分散到多个组件中,由此产生的混乱通常比您根本不费心拆分事情更糟糕。
Vue 允许您以多种方式拆分 JavaScript 代码,组件只是其中一种。将.js 文件、插件、过滤器、Vuex、mixins 等分开。您可以使用多种选项。
另一方面,模板只能通过使用组件来真正拆分。如果您想将一个巨大的模板分解成更易于管理的块,那么组件确实是唯一的出路。
这给我们带来了使用组件的另一个关键原因。
模板被编译成render 函数。当 render 函数运行时,它会注册响应式依赖项,就像计算属性一样。如果这些依赖项中的任何一个发生更改,它将触发重新渲染。这将再次运行整个 render 函数。即使这不会导致 DOM 发生任何变化,它也需要生成所有相关的 VNode,并且差异算法将需要检查所有这些。
组件边界也是渲染边界。每个组件根据其依赖关系是否发生变化来决定是否渲染。
因此,以nav-sidebar 为例,假设nav-sidebar 发生了一些变化,因此需要更新渲染。如果nav-sidebar 是一个单独的组件,那么它只需要为该组件运行模板/render 函数。相反,如果我们将所有 nav-sidebar 代码捆绑到主模板中,那么我们将不得不重新渲染所有内容。
Vue 还支持延迟加载组件,以减少页面的初始加载时间。这个想法是,许多应用程序都有很大的部分,例如管理界面,与大多数用户无关。您可以将组件拆分为块并在需要时下载这些块,而不是产生下载所有这些组件的开销。这通常通过 Vue 路由器配置来实现。
抛开分块不谈,使用路由器的典型方法是为不同的页面设置单独的组件。虽然理论上可以为所有路由使用相同的组件,这不太可能导致更易于维护的东西。我要补充一点,'page' 的定义在这里有点模糊,但在大多数应用程序中,什么构成不同的页面很清楚,从而导致不同的组件。
如果不提及测试,任何关于创建代码单体的著作都是不完整的。单元测试应该被认为是一种重用形式,并且是一种特别极端的形式。测试有一个不屈不挠的诀窍,可以揭露隐藏在你认为漂亮、干净的设计背后的意大利面条的混乱。我不会对测试发表任何看法,但我只想说除非你把东西分成合适的单元,否则你将无法编写单元测试。
组件的另一个关键特性是它有自己的一组属性。它自己的data 和它自己的计算属性。这听起来很明显,但当您考虑通过v-for 循环时,它会变得很重要。
<div v-for="item in items">...</div>
上面的例子使用内联元素而不是组件。任何状态都只能依赖于父状态。需要为每个循环项保存该状态,因此我们最终可能会得到多个数组来保存状态的不同方面。在使用循环时,计算属性同样难以实现。我们通常最终会使用方法:
<div v-for="item in items" :class="getClassesFor(item)">...</div>
现在考虑组件版本:
<my-component v-for="item in items" :item="item" />
每个my-component 都可以保持自己的状态。例如,假设my-component 具有展开/折叠功能。现在可以将其存储在每个实例中的本地 data 属性中。同样,每个实例都有自己的计算属性。
可以说这只是重用,因为v-for 创建了多个实例。但是,鉴于我们专门引入了一个新组件只是为了提供一种属性范围,我认为它值得特别提及。