Skip to content

Vue3 单文件组件规范

组件顶级元素的顺序

  • templatescriptstyle 顺序必须一致,之间空一行隔开
vue
<template>
  <section class="container">
    <AButton>自定义</AButton>
  </section>
</template>

<script setup lang="ts">
defineOptions({
  name: "CustomButton",
});
</script>

<style lang="less" scoped>
.container {
  width: 100%;
  height: auto;
}
</style>

组件名由多个单词组成

  • 这样做可以避免跟现有以及未来 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的
typescript
// Bad
export default defineComponent({
  name: "Todo",
  // ...
});

// Good
export default defineComponent({
  name: "TodoComponent",
  // ...
});

组件名应为完整单词而非缩写

  • 编辑器中自动补全已经让书写长命名的代价非常之低,而其带来的明确性却是非常宝贵的
bash
  # Bad
  components/
    |- SdSettings.vue
    |- UProOpts.vue

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

组件名中单词顺序按语境排序

  • 组件名应该以高级别的(通常是一般化描述的)单词开头,以描述性的修饰词结尾,组件间排序关系一目了然
bash
  # Bad
  components/
    |- ClearSearchButton.vue
    |- RunSearchButton.vue

  # Good
  components/
    |- SearchButtonClear.vue
    |- SearchButtonRun.vue

高耦合子组件名以父组件名为前缀

  • 如果一个组件只在某个父组件的场景下有意义,这层关系应该体现在其名字或目录上
bash
  # Bad
  components/
    |- TodoList.vue
    |- TodoItem.vue
    |- TodoButton.vue

  # Good
  components/
    |- TodoList.vue
    |- TodoListItem.vue
    |- TodoListItemButton.vue

  # Good
  components/
    |- TodoList/
    |- |- index.vue
    |- |- Item.vue
    |- |- ItemButton.vue

单文件组件文件名应 PascalCase 命名

  • 单文件组件的文件名应该始终是单词大写开头 PascalCase
bash
  # Bad
  components/
    |- mycomponent1.vue
    |- myComponent2.vue
    |- Mycomponent3.vue
    |- my-component4.vue

  # Good
  components/
    |- MyComponent1.vue
    |- MyComponent2.vue
    |- MyComponent3.vue
    |- MyComponent4.vue

.vue 单文件模板应该只包含简单的表达式

  • 组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法
vue
<!-- Bad -->
<template>
  <div class="container">
    {{
      fullName
        .split(" ")
        .map((word) => word[0].toUpperCase() + word.slice(1))
        .join(" ")
    }}
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";

const fullName = ref("todo component");
</script>

<!-- Good -->
<template>
  <div class="container">{{ computedFullName }}</div>
</template>

<script setup lang="ts">
import { ref, computed } from "vue";

const fullName = ref("todo component");

const computedFullName = computed(() => {
  const names = fullName.value.split(" ");
  return names.map((word) => word[0].toUpperCase() + word.slice(1)).join(" ");
});
</script>

.vue 单文件的自闭合组件应省略闭合标签

  • 自闭合组件表示它们不仅没有内容,没有了额外的闭合标签,代码也更简洁
vue
<!-- Bad -->
<template>
  <MyComponent></MyComponent>
</template>

<!-- Good -->
<template>
  <MyComponent />
</template>

.html 文件的自闭合组件不能省略闭合标签

  • HTML 并不支持自闭合的自定义元素——只有官方的“空”元素
vue
<!-- Bad -->
<template>
  <section>
    <div />
  </section>
</template>

<!-- Good -->
<template>
  <section>
    <div></div>
  </section>
</template>

.vue 单文件的组件以 PascalCase 方式引用

  • 采用 PascalCase 风格,具有较高可读性,同时避免跟现有的以及未来的 HTML 元素相冲突
vue
<!-- Bad -->
<template>
  <new-component />
</template>

<script>
import NewComponent from "NewComponent";
</script>

<!-- Good -->
<template>
  <NewComponent />
</template>

<script>
import NewComponent from "NewComponent";
</script>

.jsx/.tsx 文件的组件以 PascalCase 方式使用

  • 使得代码的读者更容易分辨 Vue 组件和 HTML 元素
typescript
   // Bad
   <script lang="ts">
   import NewComponent from 'NewComponent'

   export default defineComponent({
     name: 'TodoComponent',
     render () {
       return () => <new-component>
     }
   })
   </script>

   // Good
   <script lang="ts">
   import NewComponent from 'NewComponent'

   export default defineComponent({
     name: 'TodoComponent',
     render () {
       return () => <NewComponent>
     }
   }
   </script>

组件多个 attribute 应该分多行撰写

  • 组件多个 attribute 元素每个一行,更具可读性
typescript
  // Bad
  <script lang="ts">
  import NewComponent from 'NewComponent'

  export default defineComponent({
    name: 'TodoComponent',
    render () {
      return () => <NewComponent type="button" color="#f34d4d">
    }
  })
  </script>

  // Good
  <script lang="ts">
  import NewComponent from 'NewComponent'

  export default defineComponent({
    name: 'TodoComponent',
    render () {
      return () => (
        <NewComponent
          type="button"
          color="#f34d4d"
        >
      )
    }
  }
  </script>

组件的 Prop 定义尽量详细

  • prop 定义尽量详细,至少需要指定类型,如果提供不正确的 prop,Vue 会帮助你捕获错误
typescript
  // Bad
  export default defineComponent({
    props: ['status'],
  })

  // Good
  import { PropType } from 'vue'

  export default defineComponent({
    props: {
      status: {
        type: String as PropType<string>,
        default: '',
      },
    },
  })

  // Good
  import * as VueTypes from 'vue-types'

  export default defineComponent({
    props: {
      status: VueTypes.string().def(''),
    },
  })

  // Good
  <script setup lang="ts">
  export interface Props {
    status?: string
  }

  const props = defineProps<Props>()
  </script>

组件的 Prop 名应为驼峰式

  • 在声明 prop 及 模板和 JSX 使用时,其命名应使用 camelCase
vue
<!-- Bad -->
<template>
  <welcome-message :greeting-text="greetingText" />
</template>

<script>
export default defineComponent({
  name: "WelcomeMessage",
  props: {
    "greeting-text": VueTypes.string().def(""),
  },
});
</script>

<!-- Good -->
<template>
  <WelcomeMessage :greetingText="greetingText" />
</template>

<script lang="ts">
export default defineComponent({
  name: "WelcomeMessage",
  props: {
    greetingText: VueTypes.string().def(""),
  },
});
</script>

<!-- Good -->
<template>
  <WelcomeMessage :greetingText="greetingText" />
</template>

<script setup lang="ts">
export interface Props {
  greetingText?: string;
}
defineProps<Props>();
</script>

避免将 v-if 和 v-for 用在一起

  • 为了不渲染本应该隐藏的列表, 则可将 v-if 移动至其父容器元素上
vue
<!-- Bad -->
<template>
  <ul>
    <li v-for="user in users" v-if="shouldShowUsers" :key="user.id">
      {{ user.name }}
    </li>
  </ul>
</template>

<!-- Good -->
<template>
  <ul v-if="shouldShowUsers">
    <li v-for="user in users" :key="user.id">{{ user.name }}</li>
  </ul>
</template>
  • 根据某属性过滤列表中的项目, 则可替换为一个计算属性, 让其返回过滤后的列表
vue
<!-- Bad -->
<template>
  <ul>
    <li v-for="user in users" v-if="user.isActive" :key="user.id">
      {{ user.name }}
    </li>
  </ul>
</template>

<!-- Good -->
<template>
  <ul>
    <li v-for="user in activeUsers" :key="user.id">{{ user.name }}</li>
  </ul>
</template>

必须为 v-for 设置键值 key

  • 在组件上总是必须用 key 配合 v-for,以便维护内部组件及其子树的状态
vue
<!-- Bad -->
<template>
  <ul>
    <li v-for="todo in todos">{{ todo.text }}</li>
  </ul>
</template>

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

应为组件样式设置作用域

  • 基于有作用域的样式可以避免与其他组件的样式发生冲突
vue
<!-- Bad -->
<template>
  <button class="btn btn-close">X</button>
</template>

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

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

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

组件/实例各指令采用简写

  • 用 : 表示 v-bind: , @ 表示 v-on: , # 表示 v-slot:
vue
<!-- Bad -->
<template>
  <div class="container">
    <template v-slot:header>
      <h1>A page title</h1>
    </template>
    <input v-bind:value="newValue" v-on:input="onInput" />
  </div>
</template>

<!-- Good -->
<template>
  <div class="container">
    <template #header>
      <h1>A page title</h1>
    </template>
    <input :value="newValue" @input="onInput" />
  </div>
</template>

组件/实例的选项顺序

  • 如果使用 Vue2 Options 方式开发组件, 那么组件/实例的选项应该有统一的顺序
bash
  # 副作用 (触发组件外的影响)
  el

  # 全局感知 (要求组件以外的知识)
  name
  parent

  # 组件类型 (更改组件的类型)
  functional

  # 模板修改器 (改变模板的编译方式)
  delimiters
  comments

  # 模板依赖 (模板内使用的资源)
  components
  directives
  filters

  # 组合 (向选项里合并 property)
  extends
  mixins

  # 接口 (组件的接口)
  inheritAttrs
  model
  props/propsData

  # 本地状态 (本地的响应式 property)
  data
  computed

  # 监听事件 (通过响应式事件触发的回调)
  watch

  # 生命周期钩子 (按照它们被调用的顺序)
  beforeCreate
  created
  beforeMount
  mounted
  beforeUpdate
  updated
  activated
  deactivated
  beforeDestroy
  destroyed

  # 非响应式的 property
  methods

  # 渲染 (组件输出的声明式描述)
  template/render
  renderError

组件/实例的属性顺序

  • 组件/实例的属性应该有统一的顺序
bash
  # 引用 (提供组件的引用)
  is
  id
  ref

  # 双向绑定 (把绑定和事件结合起来)
  v-model

  # 列表渲染 (创建多个变化的相同元素)
  v-for

  # 条件渲染 (元素是否渲染/显示)
  v-if
  v-else-if
  v-else
  v-show
  v-cloak

  # 其他属性 (attribute 或 prop)
  key
  ...

  # 渲染方式 (改变元素的渲染方式)
  v-pre
  v-once
  v-html
  v-text

  # 事件 (组件事件监听器)
  v-on

基于 MIT 许可发布