优先级B规则:强烈推荐
这些规则在大多数项目中已被发现可以提高可读性和/或开发者体验。即使违反了这些规则,代码仍然可以运行,但违规应该很少并且有充分的理由。
组件文件
每当有可用的构建系统来连接文件时,每个组件都应该在其自己的文件中。
这有助于您在需要编辑组件或查看其使用方法时更快地找到组件。
差
js
app.component('TodoList', {
// ...
})
app.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
基本组件名称
应用特定样式和约定的基本组件(也称为展示性、哑或纯组件)都应该以特定的前缀开头,例如 Base
、App
或 V
。
详细说明
这些组件为您应用程序中一致的风格和行为奠定了基础。它们可能 只 包含
- HTML 元素,
- 其他基本组件,和
- 第三方 UI 组件。
但它们 永远不会 包含全局状态(例如来自 Pinia 商店)。
它们的名称通常包含它们所包含的元素的名称(例如 BaseButton
、BaseTable
),除非不存在特定目的的元素(例如 BaseIcon
)。如果您为更具体的环境构建类似的组件,它们几乎总是使用这些组件(例如 BaseButton
可能在 ButtonSubmit
中使用)。
此约定的优点
当在编辑器中以字母顺序组织时,您应用程序的基本组件都列在一起,这使得它们更容易识别。
由于组件名称应该是多词的,因此此约定防止您在选择简单的组件包装器时选择任意前缀(例如
MyButton
、VueButton
)。由于这些组件被频繁使用,您可能希望简单地使它们全局,而不是在所有地方导入。前缀使 Webpack 能够实现这一点
jsconst requireComponent = require.context( './src', true, /Base[A-Z]\w+\.(vue|js)$/ ) requireComponent.keys().forEach(function (fileName) { let baseComponentConfig = requireComponent(fileName) baseComponentConfig = baseComponentConfig.default || baseComponentConfig const baseComponentName = baseComponentConfig.name || fileName.replace(/^.+\//, '').replace(/\.\w+$/, '') app.component(baseComponentName, baseComponentConfig) })
差
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
紧密耦合的组件名称
与父组件紧密耦合的子组件应包含父组件名称作为前缀。
如果一个组件仅在单个父组件的上下文中才有意义,那么这种关系应该在其名称中体现出来。由于编辑器通常按字母顺序组织文件,这也使这些相关文件彼此相邻。
详细说明
您可能会被诱惑通过在以父组件命名的目录中嵌套子组件来解决此问题。例如
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
组件名称中的单词顺序
组件名称应从最高级(通常是通用性最强的)词语开始,并以描述性修饰词语结束。
详细说明
您可能会想
"为什么我们要强迫组件名称使用不太自然的语言?"
在自然英语中,形容词和其他描述词通常出现在名词之前,而例外需要连接词。例如
- Coffee with milk
- Soup of the day
- Visitor to the museum
您当然可以将这些连接词包含在组件名称中,但如果您愿意,顺序仍然很重要。
请注意,"最高级"是什么将取决于您的应用程序。例如,想象一个带有搜索表单的应用程序。它可能包括这样的组件
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
由于编辑器通常按字母顺序组织文件,因此现在可以一目了然地看到组件之间的重要关系。
您可能会想用不同的方法解决这个问题,将所有搜索组件嵌套在“搜索”目录下,然后将所有设置组件嵌套在“设置”目录下。我们只建议在非常大的应用程序(例如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
自闭合组件
在Single-File Components
、字符串模板和JSX中,没有内容的组件应该是自闭合的——但永远不会在DOM模板中使用。
自闭合的组件不仅表明它们没有内容,而且有意没有内容。这与一本书中的空白页和标记为“此页有意留白”的空白页之间的区别一样。您的代码在没有必要关闭标签的情况下也更干净。
不幸的是,HTML不允许自定义元素自闭合——只有官方的“void”元素。这就是为什么只有在Vue的模板编译器能够在DOM之前到达模板时,这种方法才可行,然后提供符合DOM规范的HTML。
差
template
<!-- In Single-File Components, string templates, and JSX -->
<MyComponent></MyComponent>
template
<!-- In in-DOM templates -->
<my-component/>
好
template
<!-- In Single-File Components, string templates, and JSX -->
<MyComponent/>
template
<!-- In in-DOM templates -->
<my-component></my-component>
模板中的组件名称大小写
在大多数项目中,组件名称应始终在Single-File Components
和字符串模板中使用PascalCase - 但在DOM模板中使用kebab-case。
PascalCase比kebab-case有一些优点
- 编辑器可以在模板中自动完成组件名称,因为PascalCase也用于JavaScript。
<MyComponent>
与单词HTML元素相比,更具有视觉上的区分度,因为有两个字符差异(两个大写字母),而不是只有一个(一个连字符)。- 如果您在模板中使用任何非Vue自定义元素,例如一个Web组件,PascalCase可以确保您的Vue组件保持明显可见。
不幸的是,由于HTML的大小写不敏感,DOM模板仍然必须使用kebab-case。
此外,请注意,如果您已经大量投资于kebab-case,与HTML约定的一致性以及能够在所有项目中使用相同的命名方式可能比上述优点更重要。在这些情况下,在所有地方使用kebab-case也是可接受的。
差
template
<!-- In Single-File Components and string templates -->
<mycomponent/>
template
<!-- In Single-File Components and string templates -->
<myComponent/>
template
<!-- In in-DOM templates -->
<MyComponent></MyComponent>
好
template
<!-- In Single-File Components and string templates -->
<MyComponent/>
template
<!-- In in-DOM templates -->
<my-component></my-component>
OR
template
<!-- Everywhere -->
<my-component></my-component>
JS/JSX中的组件名称大小写
JS/JSX中的组件名称应始终为PascalCase,尽管在简单的应用程序中,在字符串中可能使用kebab-case,这些应用程序仅通过app.component
进行全局组件注册。
详细说明
在JavaScript中,PascalCase是类和原型构造函数的约定——本质上,任何可以具有独特实例的东西。Vue组件也有实例,所以使用PascalCase也是合理的。作为额外的优势,在JSX(和模板)中使用PascalCase允许代码的读者更容易地区分组件和HTML元素。
然而,对于仅通过app.component
使用全局组件定义的应用程序,我们建议使用短横线命名法。原因如下:
- 全局组件在JavaScript中很少被引用,所以遵循JavaScript的命名约定意义不大。
- 这些应用程序通常包含许多DOM模板,在这些模板中必须使用短横线命名法。
差
js
app.component('myComponent', {
// ...
})
js
import myComponent from './MyComponent.vue'
js
export default {
name: 'myComponent'
// ...
}
js
export default {
name: 'my-component'
// ...
}
好
js
app.component('MyComponent', {
// ...
})
js
app.component('my-component', {
// ...
})
js
import MyComponent from './MyComponent.vue'
js
export default {
name: 'MyComponent'
// ...
}
完整单词组件名
组件名应优先使用完整单词而非缩写。
编辑器中的自动完成功能使编写较长的名称的成本非常低,而这些名称提供的清晰度是无价的。特别是,应始终避免不常见的缩写。
差
components/
|- SdSettings.vue
|- UProfOpts.vue
好
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
属性名命名规范
属性名在声明时始终应使用驼峰命名法。在DOM模板内部使用时,属性应使用短横线命名法。单文件组件模板和JSX可以使用短横线命名法或驼峰命名法的属性。命名法应保持一致 - 如果您选择使用驼峰命名法的属性,请确保在您的应用程序中不要使用短横线命名法的属性。
差
js
const props = defineProps({
'greeting-text': String
})
template
// for in-DOM templates
<welcome-message greetingText="hi"></welcome-message>
好
js
const props = defineProps({
greetingText: String
})
template
// for SFC - please make sure your casing is consistent throughout the project
// you can use either convention but we don't recommend mixing two different casing styles
<WelcomeMessage greeting-text="hi"/>
// or
<WelcomeMessage greetingText="hi"/>
template
// for in-DOM templates
<welcome-message greeting-text="hi"></welcome-message>
多属性元素
具有多个属性的元素应跨多行,每行一个属性。
在JavaScript中,将具有多个属性的对象拆分到多行是一种广泛认可的约定,因为它更容易阅读。我们的模板和JSX也应受到同样的重视。
差
template
<img src="https://vuejs.ac.cn/images/logo.png" alt="Vue Logo">
template
<MyComponent foo="a" bar="b" baz="c"/>
好
template
<img
src="https://vuejs.ac.cn/images/logo.png"
alt="Vue Logo"
>
template
<MyComponent
foo="a"
bar="b"
baz="c"
/>
模板中的简单表达式
组件模板应仅包含简单表达式,将更复杂的表达式重构为计算属性或方法。
模板中的复杂表达式使它们不那么声明化。我们应该努力描述应该出现的内容,而不是我们如何计算该值。计算属性和方法还允许代码重用。
差
template
{{
fullName.split(' ').map((word) => {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}
好
template
<!-- In a template -->
{{ normalizedFullName }}
js
// The complex expression has been moved to a computed property
const normalizedFullName = computed(() =>
fullName.value
.split(' ')
.map((word) => word[0].toUpperCase() + word.slice(1))
.join(' ')
)
简单的计算属性
复杂的计算属性应尽可能拆分为尽可能多的简单属性。
详细说明
简单、命名良好的计算属性是:
更容易测试
当每个计算属性只包含一个非常简单的表达式,并且依赖项非常少时,编写测试来确认它是否正确工作会容易得多。
更容易阅读
简化计算属性迫使您给每个值赋予一个描述性的名称,即使它不被重用。这使得其他开发人员(以及未来的您)更容易专注于他们关心的代码,并弄清楚发生了什么。
更适应变化的需求
任何可以命名的值可能对视图都有用。例如,我们可能决定显示一条消息,告诉用户他们节省了多少钱。我们可能还决定计算销售税,但可能将其单独显示,而不是作为最终价格的一部分。
小型、专注的计算属性对信息如何使用的假设更少,因此当需求变化时需要较少的重构。
差
js
const price = computed(() => {
const basePrice = manufactureCost.value / (1 - profitMargin.value)
return basePrice - basePrice * (discountPercent.value || 0)
})
好
js
const basePrice = computed(
() => manufactureCost.value / (1 - profitMargin.value)
)
const discount = computed(
() => basePrice.value * (discountPercent.value || 0)
)
const finalPrice = computed(() => basePrice.value - discount.value)
引号属性值
非空HTML属性值应始终在引号内(单引号或双引号,根据JavaScript中未使用的引号类型而定)。
虽然没有空格的属性值在HTML中不需要引号,但这种做法通常会导致避免空格,使得属性值不那么易于阅读。
差
template
<input type=text>
template
<AppSidebar :style={width:sidebarWidth+'px'}>
好
template
<input type="text">
template
<AppSidebar :style="{ width: sidebarWidth + 'px' }">
指令简写
指令简写(:
代表 v-bind:
,@
代表 v-on:
,#
代表 v-slot:
)应始终使用或从不使用。
差
template
<input
v-bind:value="newTodoText"
:placeholder="newTodoInstructions"
>
template
<input
v-on:input="onInput"
@focus="onFocus"
>
template
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
好
template
<input
:value="newTodoText"
:placeholder="newTodoInstructions"
>
template
<input
v-bind:value="newTodoText"
v-bind:placeholder="newTodoInstructions"
>
template
<input
@input="onInput"
@focus="onFocus"
>
template
<input
v-on:input="onInput"
v-on:focus="onFocus"
>
template
<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
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>