Vue
为 v-for
设置 key 值
始终以 key
配合 v-for
。
::: codeStyle bad
<ul>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ul>
:::
::: codeStyle good
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
:::
避免 v-if
和 v-for
一起使用
永远不要在一个元素上同时使用 v-if
和 v-for
。
::: codeStyle bad
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
:::
::: codeStyle good
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
<ul>
<template v-for="user in users" :key="user.id">
<li v-if="user.isActive">
{{ user.name }}
</li>
</template>
</ul>
:::
单文件组件文件的大小写
单文件组件的文件名应该始终是单词大写开头 (PascalCase) ::: codeStyle bad
components/
|- mycomponent.vue
components/
|- myComponent.vue
:::
::: codeStyle good
components/
|- MyComponent.vue
:::
基础组件名称
应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base
、App
或 V
。
::: codeStyle bad
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
:::
::: codeStyle good
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue
:::
单例组件名称
只应该拥有单个活跃实例的组件应该以The
前缀命名,以示其唯一性。
这并不意味着组件只可被用于一个页面,而是每个页面只能使用一次。这些组件永远不接受任何 prop,因为它们是为你的应用所定制的,而不是它们所在的上下文。如果你发现有必要添加 prop,那就表明这实际上是一个可复用的组件,只不过目前在每个页面里只使用一次。
::: codeStyle bad
components/
|- Heading.vue
|- MySidebar.vue
:::
::: codeStyle good
components/
|- TheHeading.vue
|- TheSidebar.vue
:::
紧密耦合的组件名称
与父组件紧密耦合的子组件应该以父组件名作为前缀命名。
如果一个组件只在某个特定父组件的上下文中有意义,那么这层关系应该体现在其命名上。因为编辑器通常会按字母顺序组织文件,这么做也可以把相关联的文件排放在一起。
::: codeStyle bad
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue
:::
::: codeStyle good
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue
:::
组件名称中的单词顺序
组件名称应该以高阶的 (通常是一般化描述的) 单词开头,并以描述性的修饰词结尾。
::: codeStyle bad
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
:::
::: codeStyle good
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue
:::
模板中的组件名称大小写
在单文件(SFC)组件和字符串模板中,组件名称应该始终是 PascalCase
的——但是在 DOM 模板中是 kebab-case
的。
::: codeStyle bad
<!-- SFC -->
<template>
<my-component></my-component>
</template>
<!-- html -->
<MyComponent></MyComponent>
:::
::: codeStyle good
<!-- SFC -->
<template>
<MyComponent></MyComponent>
</template>
<!-- html -->
<template>
<my-component></my-component>
</template>
:::
完整单词的组件名称
组件名称应该倾向于完整的单词,而不是缩写。
编辑器中的自动补全已经让书写长命名的代价非常之低了,而其带来的明确性却是非常宝贵的。不常用的缩写尤其应该避免。
::: codeStyle bad
components/
|- SdSettings.vue
|- UProfOpts.vue
:::
::: codeStyle good
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
:::
组件包含图片或其他文件
组件包含图片或其他文件时且资源文件只在此组件使用时,组件应在同名文件夹下。
若此组件包含子组件,且子组件仅在此组件中使用,应在组件目录中新建components
文件夹存放子组件文件
若图片在多个组件下使用,应放入公共文件夹。
::: codeStyle bad
components/
|- studentDashboardImg
| |- logo.png
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
:::
::: codeStyle good
components/
|- StudentDashboardSettings
| |- components
| |- SpecialDashBoard.vue
| |- img
| |- logo.png
| |- StudentDashboardSettings.vue
|- UserProfileOptions.vue
:::
Prop 命名
在声明 prop 的时候,其命名应该始终使用 camelCase,而在模板和 JSX 中应该始终使用 kebab-case。
我们只是单纯地遵循了每种语言的约定。在 JavaScript 中 camelCase 更为自然。而在 HTML 中则是 kebab-case。
::: codeStyle bad
props: {
'greeting-text': String
}
<WelcomeMessage greetingText="hi" />
:::
::: codeStyle good
props: {
greetingText: String
}
<WelcomeMessage greeting-text="hi" />
:::
模板中的简单表达式
组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。
复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该显示什么,而非如何计算那个值。而且计算属性和方法使得代码可以复用。
::: codeStyle bad
<template>
{{
fullName
.split(' ')
.map(word => {
return word[0].toUpperCase() + word.slice(1)
})
.join(' ')
}}
</template>
:::
::: codeStyle good
<template>
{{ normalizedFullName }}
</template>
// 复杂表达式已经转为了一个计算属性
computed: {
normalizedFullName() {
return this.fullName.split(' ')
.map(word => word[0].toUpperCase() + word.slice(1))
.join(' ')
}
}
:::
带引号的 attribute 值
非空 HTML attribute 的值应该始终带有双引号。
虽然在 HTML 中不带空格的 attribute 的值是可以不加引号的,但这样做往往导致大家避开空格,从而使得 attribute 的可读性变差。
::: codeStyle bad
<input type=text />
<template>
<AppSidebar :style={width:sidebarWidth+'px'}>
</template>
:::
::: codeStyle good
<input type="text" />
<template>
<AppSidebar :style="{width:sidebarWidth+'px'}">
</template>
:::
指令缩写
指令缩写 (用 : 表示 v-bind:,@ 表示 v-on: 和用 # 表示 v-slot) 始终使用缩写。
::: codeStyle bad
<input
v-bind:value="newTodoText"
v-on:input="onInput"
>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
:::
::: codeStyle good
<input
:value="newTodoText"
@input="onInput"
>
<template #header>
<h1>Here might be a page title</h1>
</template>
:::
单文件组件的顶级元素的顺序
单文件组件应始终保持 <template>
、<script>
和 <style>
标签的顺序一致。
::: codeStyle bad
<style>
/* ... */
</style>
<script>
/* ... */
</script>
<template>...</template>
<!-- ComponentA.vue -->
<script>
/* ... */
</script>
<template>...</template>
<style>
/* ... */
</style>
:::
::: codeStyle good
<script>
/* ... */
</script>
<template>...</template>
<style>
/* ... */
</style>
:::
隐性的父子组件通信
应该优先通过 prop
和事件进行父子组件之间的通信,而不是通过 this.$parent
或对 prop
做出变更。
一个理想的 Vue 应用是 prop
向下传递,事件向上传递的。遵循这一约定会让你的组件更易于理解。然而,在一些边界情况下,对 prop
的变更或 this.$parent
能够简化两个深度耦合的组件。
问题在于,这种做法在很多简单的场景下也可能会更方便。但请当心:不要为了一时方便 (少写代码) 而牺牲简明性 (易于理解的状态流)。
Composition API vs Options API
单文件(SFC)应该始终使用 Composition API, 且使用setup
及ref
。语法糖
当项目支持typescript
时,lang
应始终使用ts
。
<style>
除非特殊情况,都使用scope
作用域,避免样式冲突。团队使用的 CSS 预处理器应一致,暂定使用 sass
::: codeStyle bad
<template></template>
<script>
export default {
data() {
return {
foo: 1,
}
},
mounted() {},
}
</script>
<style></style>
:::
::: codeStyle good
<template></template>
<script setup lang="ts">
import { defineProps } from 'vue'
type PropType = {
foo: number
}
const props = defineProps<PropType>()
const list = $ref<Array<string>>([])
</script>
<style scoped lang="scss"></style>
:::
Template Refs 命名
以Ref
结尾来命名
::: codeStyle bad
<template>
<input ref="input" />
</template>
<script setup lang="ts">
let input = $ref<HTMLInputElement>()
</script>
:::
::: codeStyle good
<template>
<input ref="inputRef" />
</template>
<script setup lang="ts">
let inputRef = $ref<HTMLInputElement>()
</script>
:::
defineEmits
始终使用配合 Typescript 声明来使用,使用方法
::: codeStyle bad
<script setup lang="ts">
let emits = defineEmits(['listChange'])
</script>
:::
::: codeStyle good
<script setup lang="ts">
let emits = defineEmits<{
(e: 'listChange', list: Array<TodoItem>): void
}>()
</script>
:::
对象属性命名
在 vue 项目中,尽量避免使用 value 为对象属性命名,因为 ref 包装的 value 和对象的 value 属性多重的时候,会导致多写或者少些 value