Close
升级到 Vue 3 | Vue 2 EOL

样式指南

这是 Vue 特定代码的官方样式指南。如果你在项目中使用 Vue,这是一个很好的参考,可以避免错误、无谓争论和反模式。但是,我们不认为任何样式指南都适合所有团队或项目,因此鼓励根据以往经验、周围的技术栈和个人价值观进行有意识的偏离。

在大多数情况下,我们也避免对 JavaScript 或 HTML 的一般建议。我们不在乎你是否使用分号或尾随逗号。我们不在乎你的 HTML 是否使用单引号或双引号作为属性值。但是,有些例外情况,我们发现特定模式在 Vue 的上下文中很有帮助。

很快,我们还将提供关于强制执行的提示。 有时你只需要自律,但只要有可能,我们都会尝试向你展示如何使用 ESLint 和其他自动化流程来简化强制执行。

最后,我们将规则分为四类

规则类别

优先级 A:必不可少

这些规则有助于防止错误,因此无论如何都要学习并遵守它们。可能存在例外情况,但应该非常罕见,并且只能由那些精通 JavaScript 和 Vue 的人做出。

这些规则已被发现可以提高大多数项目的可读性和/或开发人员体验。如果你违反了它们,你的代码仍然可以运行,但违反应该很少见且有充分的理由。

在存在多个同样好的选项的情况下,可以做出任意选择以确保一致性。在这些规则中,我们描述了每个可接受的选项并建议一个默认选择。这意味着你可以随意在自己的代码库中做出不同的选择,只要你保持一致并有充分的理由。请务必有充分的理由!通过适应社区标准,你将

  1. 训练你的大脑更容易解析你遇到的大多数社区代码
  2. 能够在不修改的情况下复制和粘贴大多数社区代码示例
  3. 通常会发现新员工已经习惯了你喜欢的编码风格,至少在 Vue 方面是这样

优先级 D:谨慎使用

Vue 的一些功能是为了适应罕见的边缘情况或从旧版代码库平滑迁移而存在的。但是,如果过度使用,它们会使你的代码更难维护,甚至成为错误的来源。这些规则揭示了潜在的风险功能,描述了何时以及为何应该避免它们。

优先级 A 规则:必不可少(错误预防)

多词组件名称 必不可少

组件名称应始终为多词,除了根 App 组件和 Vue 提供的内置组件,例如 <transition><component>

防止与现有和将来的 HTML 元素冲突,因为所有 HTML 元素都是单个单词。

错误

Vue.component('todo', {
// ...
})
export default {
name: 'Todo',
// ...
}

正确

Vue.component('todo-item', {
// ...
})
export default {
name: 'TodoItem',
// ...
}

组件数据 必不可少

组件 data 必须是一个函数。

在组件上使用 data 属性时(即除了 new Vue 之外的任何地方),该值必须是一个返回对象的函数。

详细说明

data 的值为一个对象时,它将在组件的所有实例之间共享。例如,想象一个 TodoList 组件,它具有以下数据

data: {
listTitle: '',
todos: []
}

我们可能希望重用此组件,允许用户维护多个列表(例如,用于购物、愿望清单、日常琐事等)。但是,有一个问题。由于组件的每个实例都引用了相同的数据对象,因此更改一个列表的标题也会更改所有其他列表的标题。添加/编辑/删除待办事项也是如此。

相反,我们希望每个组件实例只管理自己的数据。为了实现这一点,每个实例必须生成一个唯一的数据对象。在 JavaScript 中,这可以通过在函数中返回对象来实现

data: function () {
return {
listTitle: '',
todos: []
}
}

错误

Vue.component('some-comp', {
data: {
foo: 'bar'
}
})
export default {
data: {
foo: 'bar'
}
}

正确

Vue.component('some-comp', {
data: function () {
return {
foo: 'bar'
}
}
})
// In a .vue file
export default {
data () {
return {
foo: 'bar'
}
}
}
// It's OK to use an object directly in a root
// Vue instance, since only a single instance
// will ever exist.
new Vue({
data: {
foo: 'bar'
}
})

道具定义 必不可少

道具定义应尽可能详细。

在提交的代码中,道具定义应始终尽可能详细,至少指定类型。

详细说明

详细的 道具定义 有两个优点

  • 它们记录了组件的 API,因此可以轻松查看组件的预期使用方式。
  • 在开发过程中,如果组件被提供格式错误的道具,Vue 会发出警告,帮助你发现潜在的错误来源。

错误

// This is only OK when prototyping
props: ['status']

正确

props: {
status: String
}
// Even better!
props: {
status: {
type: String,
required: true,
validator: function (value) {
return [
'syncing',
'synced',
'version-conflict',
'error'
].indexOf(value) !== -1
}
}
}

带键的 v-for 必不可少

始终在 v-for 中使用 key

v-for 中的 key 在组件上始终是必需的,以便维护子树中的内部组件状态。即使对于元素,这也是一个好习惯,可以保持可预测的行为,例如 对象恒定性 在动画中。

详细说明

假设你有一个待办事项列表

data: function () {
return {
todos: [
{
id: 1,
text: 'Learn to use v-for'
},
{
id: 2,
text: 'Learn to use key'
}
]
}
}

然后你按字母顺序对它们进行排序。在更新 DOM 时,Vue 会优化渲染以执行尽可能便宜的 DOM 更改。这可能意味着删除第一个待办事项元素,然后将其重新添加到列表的末尾。

问题是,在某些情况下,重要的是不要删除将保留在 DOM 中的元素。例如,你可能希望使用 <transition-group> 来为列表排序设置动画,或者在渲染的元素是 <input> 时保持焦点。在这些情况下,为每个项目添加一个唯一的键(例如 :key="todo.id")将告诉 Vue 如何以更可预测的方式执行。

根据我们的经验,最好始终添加一个唯一的键,这样你和你的团队就永远不必担心这些边缘情况。然后,在罕见的、性能至关重要的场景中,如果对象恒定性不是必需的,你可以做出有意识的例外。

错误

<ul>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ul>

正确

<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>

避免在 v-for 中使用 v-if 必不可少

永远不要在与 v-for 相同的元素上使用 v-if

有两种常见的情况可能会让你感到诱惑

详细说明

当 Vue 处理指令时,v-for 的优先级高于 v-if,因此此模板

<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

将类似于以下方式进行评估

this.users.map(function (user) {
if (user.isActive) {
return user.name
}
})

即使我们只为一小部分用户渲染元素,我们也必须在每次重新渲染时遍历整个列表,无论活动用户集是否发生变化。

通过遍历计算属性,像这样

computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

我们获得了以下好处

  • 仅当users数组发生相关变化时,才会重新评估过滤后的列表,从而使过滤效率更高。
  • 使用v-for="user in activeUsers",我们只在渲染期间遍历活动用户,从而使渲染效率更高。
  • 逻辑现在与表示层分离,使维护(逻辑更改/扩展)更容易。

我们从更新中获得类似的好处

<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

通过将v-if移动到容器元素,我们不再为列表中的每个用户检查shouldShowUsers。相反,我们只检查一次,如果shouldShowUsers为假,甚至不评估v-for

糟糕

<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

良好

<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

组件样式作用域必不可少

对于应用程序,顶级App组件和布局组件中的样式可能是全局的,但所有其他组件都应始终具有作用域。

这仅与单文件组件相关。它不需要使用scoped属性。作用域可以通过CSS 模块、基于类的策略(如BEM)或其他库/约定来实现。

但是,组件库应优先使用基于类的策略,而不是使用scoped属性。

这使得覆盖内部样式更容易,并且具有易于理解的类名,这些类名不会具有太高的特异性,但仍然不太可能导致冲突。

详细说明

如果您正在开发一个大型项目,与其他开发人员合作,或者有时包含第三方 HTML/CSS(例如来自 Auth0),一致的作用域将确保您的样式仅应用于它们所属的组件。

除了scoped属性之外,使用唯一的类名可以帮助确保第三方 CSS 不应用于您自己的 HTML。例如,许多项目使用buttonbtnicon类名,因此即使不使用 BEM 等策略,添加特定于应用程序和/或特定于组件的前缀(例如ButtonClose-icon)也能提供一些保护。

糟糕

<template>
<button class="btn btn-close">X</button>
</template>

<style>
.btn-close {
background-color: red;
}
</style>

良好

<template>
<button class="button button-close">X</button>
</template>

<!-- Using the `scoped` attribute -->
<style scoped>
.button {
border: none;
border-radius: 2px;
}

.button-close {
background-color: red;
}
</style>
<template>
<button :class="[$style.button, $style.buttonClose]">X</button>
</template>

<!-- Using CSS modules -->
<style module>
.button {
border: none;
border-radius: 2px;
}

.buttonClose {
background-color: red;
}
</style>
<template>
<button class="c-Button c-Button--close">X</button>
</template>

<!-- Using the BEM convention -->
<style>
.c-Button {
border: none;
border-radius: 2px;
}

.c-Button--close {
background-color: red;
}
</style>

私有属性名称必不可少

使用模块作用域使私有函数无法从外部访问。如果这不可行,请始终在插件、mixin 等中使用$_前缀作为自定义私有属性,这些属性不应被视为公共 API。然后,为了避免与其他作者的代码发生冲突,还要包含一个命名作用域(例如$_yourPluginName_)。

详细说明

Vue 使用_前缀来定义它自己的私有属性,因此使用相同的(例如_update)前缀可能会覆盖实例属性。即使您检查并确认 Vue 目前没有使用特定属性名称,也不能保证在以后的版本中不会出现冲突。

至于$前缀,它在 Vue 生态系统中的作用是特殊的实例属性,这些属性暴露给用户,因此将其用于私有属性是不合适的。

相反,我们建议将这两个前缀组合成$_,作为用户定义的私有属性的约定,以确保不会与 Vue 发生冲突。

糟糕

var myGreatMixin = {
// ...
methods: {
update: function () {
// ...
}
}
}
var myGreatMixin = {
// ...
methods: {
_update: function () {
// ...
}
}
}
var myGreatMixin = {
// ...
methods: {
$update: function () {
// ...
}
}
}
var myGreatMixin = {
// ...
methods: {
$_update: function () {
// ...
}
}
}

良好

var myGreatMixin = {
// ...
methods: {
$_myGreatMixin_update: function () {
// ...
}
}
}
// Even better!
var myGreatMixin = {
// ...
methods: {
publicMethod() {
// ...
myPrivateFunction()
}
}
}

function myPrivateFunction() {
// ...
}

export default myGreatMixin

只要有可用的构建系统来连接文件,每个组件都应放在它自己的文件中。

这有助于您在需要编辑组件或查看如何使用组件时更快地找到它。

糟糕

Vue.component('TodoList', {
// ...
})

Vue.component('TodoItem', {
// ...
})

良好

components/
|- TodoList.js
|- TodoItem.js
components/
|- TodoList.vue
|- TodoItem.vue

单文件组件的文件名应始终为 PascalCase 或始终为 kebab-case。

PascalCase 在代码编辑器中的自动完成功能方面效果最佳,因为它与我们在 JS(X) 和模板中引用组件的方式一致,只要有可能。但是,混合大小写文件名有时会在不区分大小写的文件系统上造成问题,这就是 kebab-case 也完全可以接受的原因。

糟糕

components/
|- mycomponent.vue
components/
|- myComponent.vue

良好

components/
|- MyComponent.vue
components/
|- my-component.vue

应用特定于应用程序的样式和约定的基本组件(也称为表示性、哑或纯组件)应全部以特定前缀开头,例如BaseAppV

详细说明

这些组件为应用程序中一致的样式和行为奠定了基础。它们可能包含

  • HTML 元素,
  • 其他基本组件,以及
  • 第三方 UI 组件。

但它们永远不会包含全局状态(例如来自 Vuex 存储)。

它们的名称通常包含它们包装的元素的名称(例如BaseButtonBaseTable),除非没有元素适合它们的特定目的(例如BaseIcon)。如果您为更具体的上下文构建类似的组件,它们几乎总是会使用这些组件(例如BaseButton可能会在ButtonSubmit中使用)。

此约定的几个优点

  • 在编辑器中按字母顺序排列时,您的应用程序的基本组件都列在一起,使它们更容易识别。

  • 由于组件名称应始终为多词,因此此约定可以防止您必须为简单的组件包装器选择任意前缀(例如MyButtonVueButton)。

糟糕

components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue

良好

components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue

应该只存在一个活动实例的组件应以The前缀开头,以表示只能有一个。

这并不意味着该组件只在一个页面中使用,而是它每个页面只使用一次。这些组件永远不会接受任何道具,因为它们是特定于您的应用程序的,而不是它们在应用程序中的上下文。如果您发现需要添加道具,这表明这实际上是一个可重用组件,目前每个页面只使用一次。

糟糕

components/
|- Heading.vue
|- MySidebar.vue

良好

components/
|- TheHeading.vue
|- TheSidebar.vue

与父组件紧密耦合的子组件应包含父组件名称作为前缀。

如果一个组件只有在单个父组件的上下文中才有意义,那么这种关系应该在它的名称中体现出来。由于编辑器通常按字母顺序组织文件,这也使这些相关文件彼此相邻。

详细说明

您可能很想通过将子组件嵌套在以父组件命名的目录中来解决这个问题。例如

components/
|- TodoList/
|- Item/
|- index.vue
|- Button.vue
|- index.vue

components/
|- TodoList/
|- Item/
|- Button.vue
|- Item.vue
|- TodoList.vue

不建议这样做,因为它会导致

  • 许多具有相似名称的文件,这使得在代码编辑器中快速切换文件更加困难。
  • 许多嵌套的子目录,这会增加在编辑器侧边栏中浏览组件所需的时间。

糟糕

components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue

良好

components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue

组件名称应以最高级别(通常是最通用的)词语开头,并以描述性的修饰词语结尾。

详细说明

您可能想知道

“为什么我们要强制组件名称使用不太自然的语言?”

在自然英语中,形容词和其他描述词通常出现在名词之前,而例外情况需要连接词。例如

  • 牛奶的咖啡
  • 当日
  • 参观博物馆的游客

如果您愿意,您当然可以在组件名称中包含这些连接词,但顺序仍然很重要。

还要注意,什么是“最高级别”将取决于您的应用程序的上下文。例如,想象一个带有搜索表单的应用程序。它可能包含像这样的组件

components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue

您可能已经注意到,很难看出哪些组件是特定于搜索的。现在让我们根据规则重命名组件

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputExcludeGlob.vue
|- SearchInputQuery.vue
|- SettingsCheckboxLaunchOnStartup.vue
|- SettingsCheckboxTerms.vue

由于编辑器通常按字母顺序组织文件,因此所有组件之间重要的关系现在一目了然。

您可能很想用不同的方法解决这个问题,将所有搜索组件嵌套在一个“search”目录下,然后将所有设置组件嵌套在一个“settings”目录下。我们只建议在非常大的应用程序(例如 100 多个组件)中考虑这种方法,原因如下

  • 浏览嵌套的子目录通常比滚动单个components目录需要更多时间。
  • 名称冲突(例如多个ButtonDelete.vue组件)使得在代码编辑器中快速导航到特定组件更加困难。
  • 重构变得更加困难,因为查找和替换通常不足以更新对已移动组件的相对引用。

糟糕

components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue

良好

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue

单文件组件、字符串模板和JSX中,不包含内容的组件应自闭合,但在 DOM 模板中永远不要自闭合。

自闭合的组件表明它们不仅没有内容,而且应该没有内容。这就像书中的一张空白页和一张标有“此页故意留空”的空白页之间的区别。没有不必要的结束标签,您的代码也更简洁。

不幸的是,HTML 不允许自定义元素自闭合,只有官方的“空”元素可以自闭合。这就是为什么这种策略只有在 Vue 的模板编译器可以在 DOM 之前访问模板,然后提供符合 DOM 规范的 HTML 时才可行。

糟糕

<!-- In single-file components, string templates, and JSX -->
<MyComponent></MyComponent>
<!-- In DOM templates -->
<my-component/>

良好

<!-- In single-file components, string templates, and JSX -->
<MyComponent/>
<!-- In DOM templates -->
<my-component></my-component>

在大多数项目中,组件名称应始终在单文件组件和字符串模板中为 PascalCase,但在 DOM 模板中为 kebab-case。

PascalCase 比 kebab-case 有几个优点

不幸的是,由于 HTML 不区分大小写,DOM 模板仍然必须使用 kebab-case。

另请注意,如果您已经大量投资于 kebab-case,那么与 HTML 约定保持一致以及能够在所有项目中使用相同的命名规则可能比上面列出的优势更重要。 在这些情况下,**在所有地方使用 kebab-case 也是可以接受的。**

错误

<!-- In single-file components and string templates -->
<mycomponent/>
<!-- In single-file components and string templates -->
<myComponent/>
<!-- In DOM templates -->
<MyComponent></MyComponent>

正确

<!-- In single-file components and string templates -->
<MyComponent/>
<!-- In DOM templates -->
<my-component></my-component>

<!-- Everywhere -->
<my-component></my-component>

JS/JSX 中的组件名称应始终为 PascalCase,尽管它们可能在字符串内部为 kebab-case,用于仅通过 Vue.component 使用全局组件注册的简单应用程序。

详细说明

在 JavaScript 中,PascalCase 是类和原型构造函数的约定 - 本质上,任何可以具有不同实例的东西。 Vue 组件也有实例,因此使用 PascalCase 也是有意义的。 作为额外的好处,在 JSX(和模板)中使用 PascalCase 使代码阅读者能够更容易地区分组件和 HTML 元素。

但是,对于仅通过 Vue.component 使用全局组件定义的应用程序,我们建议使用 kebab-case。 原因是

  • 全局组件很少在 JavaScript 中被引用,因此遵循 JavaScript 的约定意义不大。
  • 这些应用程序始终包含许多 DOM 内模板,其中kebab-case **必须**使用

错误

Vue.component('myComponent', {
// ...
})
import myComponent from './MyComponent.vue'
export default {
name: 'myComponent',
// ...
}
export default {
name: 'my-component',
// ...
}

正确

Vue.component('MyComponent', {
// ...
})
Vue.component('my-component', {
// ...
})
import MyComponent from './MyComponent.vue'
export default {
name: 'MyComponent',
// ...
}

组件名称应优先使用完整单词而不是缩写。

编辑器中的自动完成使编写较长名称的成本非常低,而它们提供的清晰度是无价的。 特别是,应始终避免使用不常见的缩写。

错误

components/
|- SdSettings.vue
|- UProfOpts.vue

正确

components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue

道具名称在声明时应始终使用 camelCase,但在模板和JSX 中使用 kebab-case。

我们只是遵循每种语言的约定。 在 JavaScript 中,camelCase 更自然。 在 HTML 中,kebab-case 更自然。

错误

props: {
'greeting-text': String
}
<WelcomeMessage greetingText="hi"/>

正确

props: {
greetingText: String
}
<WelcomeMessage greeting-text="hi"/>

具有多个属性的元素应跨越多行,每行一个属性。

在 JavaScript 中,将具有多个属性的对象拆分到多行被广泛认为是一个好习惯,因为它更容易阅读。 我们的模板和JSX 应该得到同样的考虑。

错误

<img src="https://vuejs.ac.cn/images/logo.png" alt="Vue Logo">
<MyComponent foo="a" bar="b" baz="c"/>

正确

<img
src="https://vuejs.ac.cn/images/logo.png"
alt="Vue Logo"
>
<MyComponent
foo="a"
bar="b"
baz="c"
/>

组件模板应仅包含简单表达式,更复杂的表达式应重构为计算属性或方法。

模板中的复杂表达式使它们不那么声明式。 我们应该努力描述应该出现的内容,而不是如何计算该值。 计算属性和方法也允许代码被重用。

错误

{{
fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}

正确

<!-- In a template -->
{{ normalizedFullName }}
// The complex expression has been moved to a computed property
computed: {
normalizedFullName: function () {
return this.fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}
}

复杂的计算属性应尽可能拆分为更简单的属性。

详细说明

更简单、命名良好的计算属性是

  • 更容易测试

    当每个计算属性仅包含一个非常简单的表达式,并且依赖项很少时,编写测试以确认它正常工作就容易得多。

  • 更容易阅读

    简化计算属性会迫使您为每个值指定一个描述性名称,即使它没有被重用。 这使得其他开发人员(以及未来的您)更容易专注于他们关心的代码并弄清楚发生了什么。

  • 更能适应不断变化的需求

    任何可以命名的值都可能对视图有用。 例如,我们可能决定显示一条消息,告诉用户他们节省了多少钱。 我们也可能决定计算销售税,但也许单独显示它,而不是作为最终价格的一部分。

    小型、专注的计算属性对如何使用信息做出的假设更少,因此随着需求的变化,需要更少的重构。

错误

computed: {
price: function () {
var basePrice = this.manufactureCost / (1 - this.profitMargin)
return (
basePrice -
basePrice * (this.discountPercent || 0)
)
}
}

正确

computed: {
basePrice: function () {
return this.manufactureCost / (1 - this.profitMargin)
},
discount: function () {
return this.basePrice * (this.discountPercent || 0)
},
finalPrice: function () {
return this.basePrice - this.discount
}
}

非空的 HTML 属性值应始终放在引号内(单引号或双引号,以 JS 中未使用的引号为准)。

虽然没有空格的属性值不需要在 HTML 中使用引号,但这种做法通常会导致避免空格,从而使属性值的可读性降低。

错误

<input type=text>
<AppSidebar :style={width:sidebarWidth+'px'}>

正确

<input type="text">
<AppSidebar :style="{ width: sidebarWidth + 'px' }">

指令简写(: 用于 v-bind:@ 用于 v-on:# 用于 v-slot)应始终使用或从不使用。

错误

<input
v-bind:value="newTodoText"
:placeholder="newTodoInstructions"
>
<input
v-on:input="onInput"
@focus="onFocus"
>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>

<template #footer>
<p>Here's some contact info</p>
</template>

正确

<input
:value="newTodoText"
:placeholder="newTodoInstructions"
>
<input
v-bind:value="newTodoText"
v-bind:placeholder="newTodoInstructions"
>
<input
@input="onInput"
@focus="onFocus"
>
<input
v-on:input="onInput"
v-on:focus="onFocus"
>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>

<template v-slot:footer>
<p>Here's some contact info</p>
</template>
<template #header>
<h1>Here might be a page title</h1>
</template>

<template #footer>
<p>Here's some contact info</p>
</template>

组件/实例选项应始终保持一致的顺序。

这是我们推荐的组件选项的默认顺序。 它们被分成几类,因此您将知道从插件中添加新属性的位置。

  1. 副作用(触发组件外部的影响)

    • el
  2. 全局感知(需要超出组件范围的知识)

    • name
    • parent
  3. 组件类型(更改组件的类型)

    • functional
  4. 模板修饰符(更改模板的编译方式)

    • delimiters
    • comments
  5. 模板依赖项(模板中使用的资产)

    • components
    • directives
    • filters
  6. 组合(将属性合并到选项中)

    • extends
    • mixins
  7. 接口(组件的接口)

    • inheritAttrs
    • model
    • props/propsData
  8. 本地状态(本地响应式属性)

    • data
    • computed
  9. 事件(由响应式事件触发的回调)

    • watch
    • 生命周期事件(按调用顺序排列)
      • beforeCreate
      • created
      • beforeMount
      • mounted
      • beforeUpdate
      • updated
      • activated
      • deactivated
      • beforeDestroy
      • destroyed
  10. 非响应式属性(独立于响应式系统的实例属性)

    • methods
  11. 渲染(组件输出的声明式描述)

    • template/render
    • renderError

元素(包括组件)的属性应始终保持一致的顺序。

这是我们推荐的组件选项的默认顺序。 它们被分成几类,因此您将知道在何处添加自定义属性和指令。

  1. 定义(提供组件选项)

    • is
  2. 列表渲染(创建相同元素的多个变体)

    • v-for
  3. 条件(是否渲染/显示元素)

    • v-if
    • v-else-if
    • v-else
    • v-show
    • v-cloak
  4. 渲染修饰符(更改元素的渲染方式)

    • v-pre
    • v-once
  5. 全局感知(需要超出组件范围的知识)

    • id
  6. 唯一属性(需要唯一值的属性)

    • ref
    • key
  7. 双向绑定(结合绑定和事件)

    • v-model
  8. 其他属性(所有未指定的绑定和未绑定属性)

  9. 事件(组件事件监听器)

    • v-on
  10. 内容(覆盖元素的内容)

    • v-html
    • v-text

您可能希望在多行属性之间添加一个空行,尤其是在选项无法在不滚动的情况下显示在屏幕上时。

当组件开始显得拥挤或难以阅读时,在多行属性之间添加空格可以使它们更容易再次浏览。 在某些编辑器(如 Vim)中,这种格式选项还可以使它们更容易用键盘导航。

正确

props: {
value: {
type: String,
required: true
},

focused: {
type: Boolean,
default: false
},

label: String,
icon: String
},

computed: {
formattedValue: function () {
// ...
},

inputClasses: function () {
// ...
}
}
// No spaces are also fine, as long as the component
// is still easy to read and navigate.
props: {
value: {
type: String,
required: true
},
focused: {
type: Boolean,
default: false
},
label: String,
icon: String
},
computed: {
formattedValue: function () {
// ...
},
inputClasses: function () {
// ...
}
}

单文件组件 应始终始终如一地对 <script><template><style> 标签进行排序,并将 <style> 放在最后,因为至少其他两个标签中的一个始终是必需的。

错误

<style>/* ... */</style>
<script>/* ... */</script>
<template>...</template>
<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>

<!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>

正确

<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>

<!-- ComponentB.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>
<!-- ComponentA.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>

<!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>

优先级 D 规则:谨慎使用(潜在危险模式)

v-if/v-else-if/v-elsekey 谨慎使用

通常情况下,最好在 v-if + v-else 中使用 key,如果它们是相同元素类型(例如,都是 <div> 元素)。

默认情况下,Vue 会尽可能高效地更新 DOM。这意味着在相同类型元素之间切换时,它只会修补现有元素,而不是将其删除并添加一个新的元素来代替它。如果这些元素实际上不应该被视为相同,这可能会导致 意外后果

不好

<div v-if="error">
Error: {{ error }}
</div>
<div v-else>
{{ results }}
</div>

<div
v-if="error"
key="search-status"
>
Error: {{ error }}
</div>
<div
v-else
key="search-results"
>
{{ results }}
</div>

scoped 中的元素选择器 谨慎使用

应避免在 scoped 中使用元素选择器。

scoped 样式中,优先使用类选择器而不是元素选择器,因为大量的元素选择器会很慢。

详细说明

为了对样式进行作用域限定,Vue 会向组件元素添加一个唯一的属性,例如 data-v-f3f3eg9。然后修改选择器,以便只选择具有此属性的匹配元素(例如 button[data-v-f3f3eg9])。

问题是,大量的元素-属性选择器(例如 button[data-v-f3f3eg9])会比类-属性选择器(例如 .btn-close[data-v-f3f3eg9])慢得多,因此应尽可能优先使用类选择器。

不好

<template>
<button>X</button>
</template>

<style scoped>
button {
background-color: red;
}
</style>

<template>
<button class="btn btn-close">X</button>
</template>

<style scoped>
.btn-close {
background-color: red;
}
</style>

隐式父子通信 谨慎使用

对于父子组件通信,应优先使用 props 和事件,而不是 this.$parent 或修改 props。

理想的 Vue 应用程序是 props 向下,事件向上。坚持这种约定会使你的组件更容易理解。但是,在某些情况下,修改 props 或 this.$parent 可以简化两个已经深度耦合的组件。

问题是,在许多简单情况下,这些模式可能会提供便利。注意:不要为了短期便利(编写更少的代码)而牺牲简单性(能够理解你的状态流)。

不好

Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: '<input v-model="todo.text">'
})
Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
methods: {
removeTodo () {
var vm = this
vm.$parent.todos = vm.$parent.todos.filter(function (todo) {
return todo.id !== vm.todo.id
})
}
},
template: `
<span>
{{ todo.text }}
<button @click="removeTodo">
X
</button>
</span>
`
})

Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: `
<input
:value="todo.text"
@input="$emit('input', $event.target.value)"
>
`
})
Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: `
<span>
{{ todo.text }}
<button @click="$emit('delete')">
X
</button>
</span>
`
})

非 Flux 状态管理 谨慎使用

Vuex 应优先用于全局状态管理,而不是 this.$root 或全局事件总线。

this.$root 上管理状态和/或使用 全局事件总线 在非常简单的案例中可能很方便,但它不适合大多数应用程序。

Vuex 是 Vue 的 官方类 Flux 实现,它不仅提供了一个集中管理状态的地方,还提供了组织、跟踪和调试状态更改的工具。它很好地集成在 Vue 生态系统中(包括完整的 Vue DevTools 支持)。

不好

// main.js
new Vue({
data: {
todos: []
},
created: function () {
this.$on('remove-todo', this.removeTodo)
},
methods: {
removeTodo: function (todo) {
var todoIdToRemove = todo.id
this.todos = this.todos.filter(function (todo) {
return todo.id !== todoIdToRemove
})
}
}
})

// store/modules/todos.js
export default {
state: {
list: []
},
mutations: {
REMOVE_TODO (state, todoId) {
state.list = state.list.filter(todo => todo.id !== todoId)
}
},
actions: {
removeTodo ({ commit, state }, todo) {
commit('REMOVE_TODO', todo.id)
}
}
}
<!-- TodoItem.vue -->
<template>
<span>
{{ todo.text }}
<button @click="removeTodo(todo)">
X
</button>
</span>
</template>

<script>
import { mapActions } from 'vuex'

export default {
props: {
todo: {
type: Object,
required: true
}
},
methods: mapActions(['removeTodo'])
}
</script>