团队Vue代码规范 - 8个需要避免的错误和6条强烈推荐的规范

509 阅读4分钟

〇. 概述

为了提高项目可维护和稳定性, 根据团队Vue开发码经验及Vue官方的风格指南, 整理出一份供编码及review用的Vue代码规范

文章提到的大部分规范都可以使用Eslint进行提示, 配置Eslint可以参考我之前写的: Eslint V9.x 在 Vue项目的配置参考及踩坑记录

此文档仅针对Vue语法,不包含 js, html, css, 命名等的规范

参考文档:

Vue风格指南: cn.vuejs.org/style-guide…

Vue的Eslint规则: eslint.vuejs.org/rules/

一. 需要避免的Vue语法错误

1. 组件的 data 必须是一个函数(选项式 API)

这样可以实现组件间的数据隔离,保证组件实例的独立性

// ✗ BAD
export default {
  data: {
    foo: 'bar'
  }
}
​
// ✓ GOOD
export default {
  data () {
    return {
      foo: 'bar'
    }
  }
}

2. 不要在同一个元素上使用v-ifv-for

当它们同时存在于一个节点上时,v-ifv-for 的优先级更高(在Vue2v-for优先级更高)。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名

如果你想控制每个循环出来的元素, 可以在外先包装一层 <template> 再在其上使用 v-for

(在vue3key 应该设置在 <template>上, 但在vue2key 应该设置在子节点上)

或者使用计算属性等语法, 将控制元素的逻辑放到<script>

相关文档:

cn.vuejs.org/guide/essen…

v3-migration.vuejs.org/zh/breaking…

v3-migration.vuejs.org/zh/breaking…

<!-- ✗ BAD -->
<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul><!-- ✓ GOOD -->
<ul>
  <template v-for="user in users" :key="user.id">
    <li v-if="user.isActive">
      {{ user.name }}
    </li>
  </template>
</ul><!-- ✔ BETTER -->
<ul>
  <!-- activeUsers为计算属性 -->
  <li
    v-for="user in activeUsers"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>

3. 将样式的作用域控制在组件内, 而不是全局

<style>标签上加上scoped属性

<!-- ✗ BAD -->
<template>
  <button class="btn btn-close">×</button>
</template><style>
.btn-close {
  background-color: red;
}
</style><!-- ✓ GOOD -->
<template>
  <button class="button button-close">×</button>
</template><!-- Using the `scoped` attribute -->
<style scoped>
.button {
  border: none;
  border-radius: 2px;
}
​
.button-close {
  background-color: red;
}
</style>

4. 不要在计算属性中执行异步操作

在计算属性中执行异步操作可能无法按预期工作, 并可能导致意外行为, 如果你需要异步的计算属性, 可以考虑使用插件vue-async-computed

// ✗ BAD
computed:{
    pro() {
      return Promise.all([new Promise((resolve, reject) => {})])
    },
}

5. 计算属性的getter中不应有副作用

副作用: 函数对外部环境进行了修改或者产生了其他可观察的影响

譬如: 修改外部变量, 修改全局变量等

在计算属性中不要产生'副作用', 这可能会导致程序的行为难以预测和理解

// ✗ BAD
const fullName = computed(() => {
   foo.firstName = 'lorem' // <- side effect
   return `${foo.firstName} ${foo.lastName}`
})
const reversedArray = computed(() => {
   return foo.array.reverse() // reverse会改变原数组
)
​
// ✓ GOOD
const fullName = computed(() => `${foo.firstName} ${foo.lastName}`)
const reversedArray = computed(() => {
   return foo.array.slice(0).reverse() // .slice 产生了一个原数组的拷贝
})

6. 不要在自定义组件上使用key属性

key是vue内置的用于虚拟 DOM diff 算法的特殊属性, 请不要使用key作为自定义组件属性

相关文档: cn.vuejs.org/api/built-i…

// ✗ BAD
defineProps:({
   key: String
})

7. computed必须有返回值

// ✗ BAD
const baz = computed(() => {
   if (foobar.baf) {
     return foobar.baf
   }
})
​
// ✓ GOOD
const foo = computed(() => {
   if (foobar.bar) {
     return foobar.baz
   } else {
     return foobar.baf
   }
})

8. 避免使用已经弃用的Vue属性

一些语法在Vue3中已被移除(如过滤器, ::v-deep等), 应避免使用这些语法

参考文档: v3-migration.vuejs.org/zh/breaking…

二. 强烈推荐的Vue代码规范

1. 使用详细的prop定义

组件的prop定义应尽可能详细, 至少要指定类型

// ✗ BAD
props: ['status']
​
// ✓ GOOD
props: { status: String }
​
// ✔ BETTER
props: {
  status: {
    type: String,
    required: true,
    validator: value => {
      return [
        'syncing',
        'synced',
        'version-conflict',
        'error'
      ].includes(value)
    }
  }
}

2. 使用v-for时需指定key

指定key可以帮助Vue实现更高效的DOM更新

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

3. 组件上不要使用复杂的表达式, 请用计算属性或方法代替

模板中复杂的表达式会降低代码可读性和可维护性, 请将复杂的计算逻辑迁移到计算属性/方法, 而且它们还允许代码复用

<!-- ✗ BAD -->
<div>{{
  fullName.split(' ').map((word) => {
    return word[0].toUpperCase() + word.slice(1)
  }).join(' ')
}}</div><!-- ✓ GOOD -->
<div>{{ normalizedFullName }}</div><!-- 将计算逻辑挪到计算属性 -->
<script>
...
computed: {
  normalizedFullName() {
    return this.fullName.split(' ')
      .map(word => word[0].toUpperCase() + word.slice(1))
      .join(' ')
  }
}
...
</script>

4. 使用简写指令

为了保持风格统一, 请使用简写属性

<!-- ✗ BAD -->
<input
  v-on:input="onInput"
  @focus="onFocus"
><!-- ✓ GOOD -->
<input
  @input="onInput"
  @focus="onFocus"
>

5. 单文件组件的顶层元素顺序

单文件组件应该总是让 <script><template><style> 标签的顺序保持一致。且 <style> 要放在最后,因为另外两个标签至少要有一个。

<!-- ✗ BAD -->
<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style><!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style><!-- ✓ GOOD -->
<!-- ComponentA.vue -->
<template>...</template>
<script>/* ... */</script>
<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>

6. 关于父子组件通信

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

一个理想的Vue应用是prop向下传递,事件向上传递的。遵循这一约定会让你的组件更易于理解。然而,在一些边界情况下prop的变更或 this.$parent 能够简化两个深度耦合的组件。问题在于,这种做法在很多简单的场景下可能会更方便。但请当心,不要为了一时方便 (少写代码) 而牺牲数据流向的简洁性 (易于理解)。

关于父子组件双向通信, 可以使用组件的v-model语法, 如果你用的是Vue3.4+可以使用更优雅的defineModel处理组件的双向绑定

可以参考我之前写的: v-module 双向绑定, 以及 VUE3.4 新语法 defineModel 的前世今生

组件的v-model: cn.vuejs.org/guide/compo…

defineModel: cn.vuejs.org/api/sfc-scr…

Vue2Vue3中组件v-model的差异: v3-migration.vuejs.org/zh/breaking…

// ✗ BAD
props.todo = xxx // 子组件内不要直接更改props属性的值// ✓ GOOD
// 父组件:
<CustomInput v-model="searchText" />
​
// 子组件:
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>
​
<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>
​
// ✔ BETTER
const model = defineModel()