Skip to content

Vue

v-for 设置 key 值

始终以 key 配合 v-for

::: codeStyle bad

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

:::

::: codeStyle good

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

:::

避免 v-ifv-for 一起使用

永远不要在一个元素上同时使用 v-ifv-for

::: codeStyle bad

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

:::

::: codeStyle good

vue
<ul>
  <li
    v-for="user in activeUsers"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>
vue
<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

:::

基础组件名称

应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 BaseAppV

::: 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

vue
<!-- SFC -->
<template>
  <my-component></my-component>
</template>
html
<!-- html -->
<MyComponent></MyComponent>

:::

::: codeStyle good

vue
<!-- SFC -->
<template>
  <MyComponent></MyComponent>
</template>
html
<!-- 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

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

:::

::: codeStyle good

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

:::

模板中的简单表达式

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

复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该显示什么,而非如何计算那个值。而且计算属性和方法使得代码可以复用。

::: codeStyle bad

vue
<template>
  {{
    fullName
      .split(' ')
      .map(word => {
        return word[0].toUpperCase() + word.slice(1)
      })
      .join(' ')
  }}
</template>

:::

::: codeStyle good

vue
<template>
  {{ normalizedFullName }}
</template>
js
// 复杂表达式已经转为了一个计算属性
computed: {
  normalizedFullName() {
    return this.fullName.split(' ')
      .map(word => word[0].toUpperCase() + word.slice(1))
      .join(' ')
  }
}

:::

带引号的 attribute 值

非空 HTML attribute 的值应该始终带有双引号。

虽然在 HTML 中不带空格的 attribute 的值是可以不加引号的,但这样做往往导致大家避开空格,从而使得 attribute 的可读性变差。

::: codeStyle bad

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

:::

::: codeStyle good

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

:::

指令缩写

指令缩写 (用 : 表示 v-bind:,@ 表示 v-on: 和用 # 表示 v-slot) 始终使用缩写。

::: codeStyle bad

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

:::

::: codeStyle good

vue
<input
  :value="newTodoText"
  @input="onInput"
>
vue
<template #header>
  <h1>Here might be a page title</h1>
</template>

:::

单文件组件的顶级元素的顺序

单文件组件应始终保持 <template><script><style> 标签的顺序一致。

::: codeStyle bad

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

:::

::: codeStyle good

vue
<script>
/* ... */
</script>
<template>...</template>
<style>
/* ... */
</style>

:::

隐性的父子组件通信

应该优先通过 prop 和事件进行父子组件之间的通信,而不是通过 this.$parent 或对 prop 做出变更。

一个理想的 Vue 应用是 prop 向下传递,事件向上传递的。遵循这一约定会让你的组件更易于理解。然而,在一些边界情况下,对 prop 的变更或 this.$parent 能够简化两个深度耦合的组件。

问题在于,这种做法在很多简单的场景下也可能会更方便。但请当心:不要为了一时方便 (少写代码) 而牺牲简明性 (易于理解的状态流)。

Composition API vs Options API

单文件(SFC)应该始终使用 Composition API, 且使用setupref。语法糖

当项目支持typescript时,lang应始终使用ts

<style>除非特殊情况,都使用scope作用域,避免样式冲突。团队使用的 CSS 预处理器应一致,暂定使用 sass

::: codeStyle bad

vue
<template></template>
<script>
export default {
  data() {
    return {
      foo: 1,
    }
  },
  mounted() {},
}
</script>
<style></style>

:::

::: codeStyle good

vue
<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

vue
<template>
  <input ref="input" />
</template>
<script setup lang="ts">
let input = $ref<HTMLInputElement>()
</script>

:::

::: codeStyle good

vue
<template>
  <input ref="inputRef" />
</template>
<script setup lang="ts">
let inputRef = $ref<HTMLInputElement>()
</script>

:::

defineEmits

始终使用配合 Typescript 声明来使用,使用方法

::: codeStyle bad

vue
<script setup lang="ts">
let emits = defineEmits(['listChange'])
</script>

:::

::: codeStyle good

vue
<script setup lang="ts">
let emits = defineEmits<{
  (e: 'listChange', list: Array<TodoItem>): void
}>()
</script>

:::

对象属性命名

在 vue 项目中,尽量避免使用 value 为对象属性命名,因为 ref 包装的 value 和对象的 value 属性多重的时候,会导致多写或者少些 value