样式指南
这是 Vue 特定代码的官方样式指南。如果你在项目中使用 Vue,这是一个很好的参考,可以避免错误、无谓争论和反模式。但是,我们不认为任何样式指南都适合所有团队或项目,因此鼓励根据以往经验、周围的技术栈和个人价值观进行有意识的偏离。
在大多数情况下,我们也避免对 JavaScript 或 HTML 的一般建议。我们不在乎你是否使用分号或尾随逗号。我们不在乎你的 HTML 是否使用单引号或双引号作为属性值。但是,有些例外情况,我们发现特定模式在 Vue 的上下文中很有帮助。
很快,我们还将提供关于强制执行的提示。 有时你只需要自律,但只要有可能,我们都会尝试向你展示如何使用 ESLint 和其他自动化流程来简化强制执行。
最后,我们将规则分为四类
规则类别
优先级 A:必不可少
这些规则有助于防止错误,因此无论如何都要学习并遵守它们。可能存在例外情况,但应该非常罕见,并且只能由那些精通 JavaScript 和 Vue 的人做出。
优先级 B:强烈推荐
这些规则已被发现可以提高大多数项目的可读性和/或开发人员体验。如果你违反了它们,你的代码仍然可以运行,但违反应该很少见且有充分的理由。
优先级 C:推荐
在存在多个同样好的选项的情况下,可以做出任意选择以确保一致性。在这些规则中,我们描述了每个可接受的选项并建议一个默认选择。这意味着你可以随意在自己的代码库中做出不同的选择,只要你保持一致并有充分的理由。请务必有充分的理由!通过适应社区标准,你将
- 训练你的大脑更容易解析你遇到的大多数社区代码
- 能够在不修改的情况下复制和粘贴大多数社区代码示例
- 通常会发现新员工已经习惯了你喜欢的编码风格,至少在 Vue 方面是这样
优先级 D:谨慎使用
Vue 的一些功能是为了适应罕见的边缘情况或从旧版代码库平滑迁移而存在的。但是,如果过度使用,它们会使你的代码更难维护,甚至成为错误的来源。这些规则揭示了潜在的风险功能,描述了何时以及为何应该避免它们。
优先级 A 规则:必不可少(错误预防)
多词组件名称 必不可少
组件名称应始终为多词,除了根 App
组件和 Vue 提供的内置组件,例如 <transition>
或 <component>
。
这 防止与现有和将来的 HTML 元素冲突,因为所有 HTML 元素都是单个单词。
组件数据 必不可少
组件 data
必须是一个函数。
在组件上使用 data
属性时(即除了 new Vue
之外的任何地方),该值必须是一个返回对象的函数。
详细说明
当 data
的值为一个对象时,它将在组件的所有实例之间共享。例如,想象一个 TodoList
组件,它具有以下数据
|
我们可能希望重用此组件,允许用户维护多个列表(例如,用于购物、愿望清单、日常琐事等)。但是,有一个问题。由于组件的每个实例都引用了相同的数据对象,因此更改一个列表的标题也会更改所有其他列表的标题。添加/编辑/删除待办事项也是如此。
相反,我们希望每个组件实例只管理自己的数据。为了实现这一点,每个实例必须生成一个唯一的数据对象。在 JavaScript 中,这可以通过在函数中返回对象来实现
|
正确
|
|
|
道具定义 必不可少
道具定义应尽可能详细。
在提交的代码中,道具定义应始终尽可能详细,至少指定类型。
正确
|
|
带键的 v-for
必不可少
始终在 v-for
中使用 key
。
v-for
中的 key
在组件上始终是必需的,以便维护子树中的内部组件状态。即使对于元素,这也是一个好习惯,可以保持可预测的行为,例如 对象恒定性 在动画中。
详细说明
假设你有一个待办事项列表
|
然后你按字母顺序对它们进行排序。在更新 DOM 时,Vue 会优化渲染以执行尽可能便宜的 DOM 更改。这可能意味着删除第一个待办事项元素,然后将其重新添加到列表的末尾。
问题是,在某些情况下,重要的是不要删除将保留在 DOM 中的元素。例如,你可能希望使用 <transition-group>
来为列表排序设置动画,或者在渲染的元素是 <input>
时保持焦点。在这些情况下,为每个项目添加一个唯一的键(例如 :key="todo.id"
)将告诉 Vue 如何以更可预测的方式执行。
根据我们的经验,最好始终添加一个唯一的键,这样你和你的团队就永远不必担心这些边缘情况。然后,在罕见的、性能至关重要的场景中,如果对象恒定性不是必需的,你可以做出有意识的例外。
避免在 v-for
中使用 v-if
必不可少
永远不要在与 v-for
相同的元素上使用 v-if
。
有两种常见的情况可能会让你感到诱惑
过滤列表中的项目(例如
v-for="user in users" v-if="user.isActive"
)。在这些情况下,将users
替换为一个新的计算属性,该属性返回你的过滤列表(例如activeUsers
)。如果应该隐藏列表,则避免渲染列表(例如
v-for="user in users" v-if="shouldShowUsers"
)。在这些情况下,将v-if
移动到容器元素(例如ul
、ol
)。
详细说明
当 Vue 处理指令时,v-for
的优先级高于 v-if
,因此此模板
|
将类似于以下方式进行评估
|
即使我们只为一小部分用户渲染元素,我们也必须在每次重新渲染时遍历整个列表,无论活动用户集是否发生变化。
通过遍历计算属性,像这样
|
|
我们获得了以下好处
- 仅当
users
数组发生相关变化时,才会重新评估过滤后的列表,从而使过滤效率更高。 - 使用
v-for="user in activeUsers"
,我们只在渲染期间遍历活动用户,从而使渲染效率更高。 - 逻辑现在与表示层分离,使维护(逻辑更改/扩展)更容易。
我们从更新中获得类似的好处
|
到
|
通过将v-if
移动到容器元素,我们不再为列表中的每个用户检查shouldShowUsers
。相反,我们只检查一次,如果shouldShowUsers
为假,甚至不评估v-for
。
糟糕
|
|
良好
|
|
组件样式作用域必不可少
对于应用程序,顶级App
组件和布局组件中的样式可能是全局的,但所有其他组件都应始终具有作用域。
这仅与单文件组件相关。它不需要使用scoped
属性。作用域可以通过CSS 模块、基于类的策略(如BEM)或其他库/约定来实现。
但是,组件库应优先使用基于类的策略,而不是使用scoped
属性。
这使得覆盖内部样式更容易,并且具有易于理解的类名,这些类名不会具有太高的特异性,但仍然不太可能导致冲突。
详细说明
如果您正在开发一个大型项目,与其他开发人员合作,或者有时包含第三方 HTML/CSS(例如来自 Auth0),一致的作用域将确保您的样式仅应用于它们所属的组件。
除了scoped
属性之外,使用唯一的类名可以帮助确保第三方 CSS 不应用于您自己的 HTML。例如,许多项目使用button
、btn
或icon
类名,因此即使不使用 BEM 等策略,添加特定于应用程序和/或特定于组件的前缀(例如ButtonClose-icon
)也能提供一些保护。
糟糕
|
良好
|
|
|
私有属性名称必不可少
使用模块作用域使私有函数无法从外部访问。如果这不可行,请始终在插件、mixin 等中使用$_
前缀作为自定义私有属性,这些属性不应被视为公共 API。然后,为了避免与其他作者的代码发生冲突,还要包含一个命名作用域(例如$_yourPluginName_
)。
详细说明
Vue 使用_
前缀来定义它自己的私有属性,因此使用相同的(例如_update
)前缀可能会覆盖实例属性。即使您检查并确认 Vue 目前没有使用特定属性名称,也不能保证在以后的版本中不会出现冲突。
至于$
前缀,它在 Vue 生态系统中的作用是特殊的实例属性,这些属性暴露给用户,因此将其用于私有属性是不合适的。
相反,我们建议将这两个前缀组合成$_
,作为用户定义的私有属性的约定,以确保不会与 Vue 发生冲突。
糟糕
|
|
|
|
良好
|
|
优先级 B 规则:强烈推荐(提高可读性)
组件文件强烈推荐
只要有可用的构建系统来连接文件,每个组件都应放在它自己的文件中。
这有助于您在需要编辑组件或查看如何使用组件时更快地找到它。
单文件组件文件名大小写强烈推荐
单文件组件的文件名应始终为 PascalCase 或始终为 kebab-case。
PascalCase 在代码编辑器中的自动完成功能方面效果最佳,因为它与我们在 JS(X) 和模板中引用组件的方式一致,只要有可能。但是,混合大小写文件名有时会在不区分大小写的文件系统上造成问题,这就是 kebab-case 也完全可以接受的原因。
基本组件名称强烈推荐
应用特定于应用程序的样式和约定的基本组件(也称为表示性、哑或纯组件)应全部以特定前缀开头,例如Base
、App
或V
。
详细说明
这些组件为应用程序中一致的样式和行为奠定了基础。它们可能只包含
- HTML 元素,
- 其他基本组件,以及
- 第三方 UI 组件。
但它们永远不会包含全局状态(例如来自 Vuex 存储)。
它们的名称通常包含它们包装的元素的名称(例如BaseButton
、BaseTable
),除非没有元素适合它们的特定目的(例如BaseIcon
)。如果您为更具体的上下文构建类似的组件,它们几乎总是会使用这些组件(例如BaseButton
可能会在ButtonSubmit
中使用)。
此约定的几个优点
在编辑器中按字母顺序排列时,您的应用程序的基本组件都列在一起,使它们更容易识别。
由于组件名称应始终为多词,因此此约定可以防止您必须为简单的组件包装器选择任意前缀(例如
MyButton
、VueButton
)。
良好
|
|
|
单实例组件名称强烈推荐
应该只存在一个活动实例的组件应以The
前缀开头,以表示只能有一个。
这并不意味着该组件只在一个页面中使用,而是它每个页面只使用一次。这些组件永远不会接受任何道具,因为它们是特定于您的应用程序的,而不是它们在应用程序中的上下文。如果您发现需要添加道具,这表明这实际上是一个可重用组件,目前每个页面只使用一次。
紧密耦合的组件名称强烈推荐
与父组件紧密耦合的子组件应包含父组件名称作为前缀。
如果一个组件只有在单个父组件的上下文中才有意义,那么这种关系应该在它的名称中体现出来。由于编辑器通常按字母顺序组织文件,这也使这些相关文件彼此相邻。
详细说明
您可能很想通过将子组件嵌套在以父组件命名的目录中来解决这个问题。例如
|
或
|
不建议这样做,因为它会导致
- 许多具有相似名称的文件,这使得在代码编辑器中快速切换文件更加困难。
- 许多嵌套的子目录,这会增加在编辑器侧边栏中浏览组件所需的时间。
糟糕
|
|
良好
|
|
组件名称中词语的顺序强烈推荐
组件名称应以最高级别(通常是最通用的)词语开头,并以描述性的修饰词语结尾。
详细说明
您可能想知道
“为什么我们要强制组件名称使用不太自然的语言?”
在自然英语中,形容词和其他描述词通常出现在名词之前,而例外情况需要连接词。例如
- 带牛奶的咖啡
- 当日汤
- 参观博物馆的游客
如果您愿意,您当然可以在组件名称中包含这些连接词,但顺序仍然很重要。
还要注意,什么是“最高级别”将取决于您的应用程序的上下文。例如,想象一个带有搜索表单的应用程序。它可能包含像这样的组件
|
您可能已经注意到,很难看出哪些组件是特定于搜索的。现在让我们根据规则重命名组件
|
由于编辑器通常按字母顺序组织文件,因此所有组件之间重要的关系现在一目了然。
您可能很想用不同的方法解决这个问题,将所有搜索组件嵌套在一个“search”目录下,然后将所有设置组件嵌套在一个“settings”目录下。我们只建议在非常大的应用程序(例如 100 多个组件)中考虑这种方法,原因如下
- 浏览嵌套的子目录通常比滚动单个
components
目录需要更多时间。 - 名称冲突(例如多个
ButtonDelete.vue
组件)使得在代码编辑器中快速导航到特定组件更加困难。 - 重构变得更加困难,因为查找和替换通常不足以更新对已移动组件的相对引用。
糟糕
|
良好
|
自闭合组件强烈推荐
在单文件组件、字符串模板和JSX中,不包含内容的组件应自闭合,但在 DOM 模板中永远不要自闭合。
自闭合的组件表明它们不仅没有内容,而且应该没有内容。这就像书中的一张空白页和一张标有“此页故意留空”的空白页之间的区别。没有不必要的结束标签,您的代码也更简洁。
不幸的是,HTML 不允许自定义元素自闭合,只有官方的“空”元素可以自闭合。这就是为什么这种策略只有在 Vue 的模板编译器可以在 DOM 之前访问模板,然后提供符合 DOM 规范的 HTML 时才可行。
糟糕
|
|
良好
|
|
模板中的组件名称大小写强烈推荐
在大多数项目中,组件名称应始终在单文件组件和字符串模板中为 PascalCase,但在 DOM 模板中为 kebab-case。
PascalCase 比 kebab-case 有几个优点
- 编辑器可以在模板中自动完成组件名称,因为 PascalCase 也在 JavaScript 中使用。
<MyComponent>
比<my-component>
在视觉上更能与单个词的 HTML 元素区分开来,因为它们有两个字符不同(两个大写字母),而不是只有一个(一个连字符)。- 如果您在模板中使用任何非 Vue 自定义元素,例如 Web 组件,PascalCase 确保您的 Vue 组件仍然清晰可见。
不幸的是,由于 HTML 不区分大小写,DOM 模板仍然必须使用 kebab-case。
另请注意,如果您已经大量投资于 kebab-case,那么与 HTML 约定保持一致以及能够在所有项目中使用相同的命名规则可能比上面列出的优势更重要。 在这些情况下,**在所有地方使用 kebab-case 也是可以接受的。**
错误
|
|
|
正确
|
|
或
|
JS/JSX 中的组件名称 强烈建议
JS/JSX 中的组件名称应始终为 PascalCase,尽管它们可能在字符串内部为 kebab-case,用于仅通过 Vue.component
使用全局组件注册的简单应用程序。
详细说明
在 JavaScript 中,PascalCase 是类和原型构造函数的约定 - 本质上,任何可以具有不同实例的东西。 Vue 组件也有实例,因此使用 PascalCase 也是有意义的。 作为额外的好处,在 JSX(和模板)中使用 PascalCase 使代码阅读者能够更容易地区分组件和 HTML 元素。
但是,对于仅通过 Vue.component
使用全局组件定义的应用程序,我们建议使用 kebab-case。 原因是
- 全局组件很少在 JavaScript 中被引用,因此遵循 JavaScript 的约定意义不大。
- 这些应用程序始终包含许多 DOM 内模板,其中kebab-case **必须**使用。
错误
|
|
|
|
正确
|
|
|
|
完整单词组件名称 强烈建议
组件名称应优先使用完整单词而不是缩写。
编辑器中的自动完成使编写较长名称的成本非常低,而它们提供的清晰度是无价的。 特别是,应始终避免使用不常见的缩写。
道具名称命名规则 强烈建议
道具名称在声明时应始终使用 camelCase,但在模板和JSX 中使用 kebab-case。
我们只是遵循每种语言的约定。 在 JavaScript 中,camelCase 更自然。 在 HTML 中,kebab-case 更自然。
多属性元素 强烈建议
具有多个属性的元素应跨越多行,每行一个属性。
在 JavaScript 中,将具有多个属性的对象拆分到多行被广泛认为是一个好习惯,因为它更容易阅读。 我们的模板和JSX 应该得到同样的考虑。
错误
|
|
正确
|
|
模板中的简单表达式 强烈建议
组件模板应仅包含简单表达式,更复杂的表达式应重构为计算属性或方法。
模板中的复杂表达式使它们不那么声明式。 我们应该努力描述应该出现的内容,而不是如何计算该值。 计算属性和方法也允许代码被重用。
错误
|
正确
|
|
简单计算属性 强烈建议
复杂的计算属性应尽可能拆分为更简单的属性。
详细说明
更简单、命名良好的计算属性是
更容易测试
当每个计算属性仅包含一个非常简单的表达式,并且依赖项很少时,编写测试以确认它正常工作就容易得多。
更容易阅读
简化计算属性会迫使您为每个值指定一个描述性名称,即使它没有被重用。 这使得其他开发人员(以及未来的您)更容易专注于他们关心的代码并弄清楚发生了什么。
更能适应不断变化的需求
任何可以命名的值都可能对视图有用。 例如,我们可能决定显示一条消息,告诉用户他们节省了多少钱。 我们也可能决定计算销售税,但也许单独显示它,而不是作为最终价格的一部分。
小型、专注的计算属性对如何使用信息做出的假设更少,因此随着需求的变化,需要更少的重构。
错误
|
正确
|
带引号的属性值 强烈建议
非空的 HTML 属性值应始终放在引号内(单引号或双引号,以 JS 中未使用的引号为准)。
虽然没有空格的属性值不需要在 HTML 中使用引号,但这种做法通常会导致避免空格,从而使属性值的可读性降低。
指令简写 强烈建议
指令简写(:
用于 v-bind:
,@
用于 v-on:
和 #
用于 v-slot
)应始终使用或从不使用。
错误
|
|
|
正确
|
|
|
|
|
|
优先级 C 规则:推荐(最小化任意选择和认知开销)
组件/实例选项顺序 推荐
组件/实例选项应始终保持一致的顺序。
这是我们推荐的组件选项的默认顺序。 它们被分成几类,因此您将知道从插件中添加新属性的位置。
副作用(触发组件外部的影响)
el
全局感知(需要超出组件范围的知识)
name
parent
组件类型(更改组件的类型)
functional
模板修饰符(更改模板的编译方式)
delimiters
comments
模板依赖项(模板中使用的资产)
components
directives
filters
组合(将属性合并到选项中)
extends
mixins
接口(组件的接口)
inheritAttrs
model
props
/propsData
本地状态(本地响应式属性)
data
computed
事件(由响应式事件触发的回调)
watch
- 生命周期事件(按调用顺序排列)
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeDestroy
destroyed
非响应式属性(独立于响应式系统的实例属性)
methods
渲染(组件输出的声明式描述)
template
/render
renderError
元素属性顺序 推荐
元素(包括组件)的属性应始终保持一致的顺序。
这是我们推荐的组件选项的默认顺序。 它们被分成几类,因此您将知道在何处添加自定义属性和指令。
定义(提供组件选项)
is
列表渲染(创建相同元素的多个变体)
v-for
条件(是否渲染/显示元素)
v-if
v-else-if
v-else
v-show
v-cloak
渲染修饰符(更改元素的渲染方式)
v-pre
v-once
全局感知(需要超出组件范围的知识)
id
唯一属性(需要唯一值的属性)
ref
key
双向绑定(结合绑定和事件)
v-model
其他属性(所有未指定的绑定和未绑定属性)
事件(组件事件监听器)
v-on
内容(覆盖元素的内容)
v-html
v-text
组件/实例选项中的空行 推荐
您可能希望在多行属性之间添加一个空行,尤其是在选项无法在不滚动的情况下显示在屏幕上时。
当组件开始显得拥挤或难以阅读时,在多行属性之间添加空格可以使它们更容易再次浏览。 在某些编辑器(如 Vim)中,这种格式选项还可以使它们更容易用键盘导航。
正确
|
|
单文件组件顶层元素顺序 推荐
单文件组件 应始终始终如一地对 <script>
、<template>
和 <style>
标签进行排序,并将 <style>
放在最后,因为至少其他两个标签中的一个始终是必需的。
错误
|
|
正确
|
|
优先级 D 规则:谨慎使用(潜在危险模式)
v-if
/v-else-if
/v-else
无 key
谨慎使用
通常情况下,最好在 v-if
+ v-else
中使用 key
,如果它们是相同元素类型(例如,都是 <div>
元素)。
默认情况下,Vue 会尽可能高效地更新 DOM。这意味着在相同类型元素之间切换时,它只会修补现有元素,而不是将其删除并添加一个新的元素来代替它。如果这些元素实际上不应该被视为相同,这可能会导致 意外后果。
好
|
scoped
中的元素选择器 谨慎使用
应避免在 scoped
中使用元素选择器。
在 scoped
样式中,优先使用类选择器而不是元素选择器,因为大量的元素选择器会很慢。
详细说明
为了对样式进行作用域限定,Vue 会向组件元素添加一个唯一的属性,例如 data-v-f3f3eg9
。然后修改选择器,以便只选择具有此属性的匹配元素(例如 button[data-v-f3f3eg9]
)。
问题是,大量的元素-属性选择器(例如 button[data-v-f3f3eg9]
)会比类-属性选择器(例如 .btn-close[data-v-f3f3eg9]
)慢得多,因此应尽可能优先使用类选择器。
好
|
隐式父子通信 谨慎使用
对于父子组件通信,应优先使用 props 和事件,而不是 this.$parent
或修改 props。
理想的 Vue 应用程序是 props 向下,事件向上。坚持这种约定会使你的组件更容易理解。但是,在某些情况下,修改 props 或 this.$parent
可以简化两个已经深度耦合的组件。
问题是,在许多简单情况下,这些模式可能会提供便利。注意:不要为了短期便利(编写更少的代码)而牺牲简单性(能够理解你的状态流)。
不好
|
|
好
|
|
非 Flux 状态管理 谨慎使用
Vuex 应优先用于全局状态管理,而不是 this.$root
或全局事件总线。
在 this.$root
上管理状态和/或使用 全局事件总线 在非常简单的案例中可能很方便,但它不适合大多数应用程序。
Vuex 是 Vue 的 官方类 Flux 实现,它不仅提供了一个集中管理状态的地方,还提供了组织、跟踪和调试状态更改的工具。它很好地集成在 Vue 生态系统中(包括完整的 Vue DevTools 支持)。
不好
|
好
|
|