〇. 概述
为了提高项目可维护和稳定性, 根据团队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-if
和v-for
当它们同时存在于一个节点上时,v-if
比 v-for
的优先级更高(在Vue2
中v-for
优先级更高)。这意味着 v-if
的条件将无法访问到 v-for
作用域内定义的变量别名
如果你想控制每个循环出来的元素, 可以在外先包装一层 <template>
再在其上使用 v-for
(在vue3
中key
应该设置在 <template>
上, 但在vue2
中key
应该设置在子节点上)
或者使用计算属性等语法, 将控制元素的逻辑放到<script>
内
相关文档:
<!-- ✗ 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
作为自定义组件属性
// ✗ 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
等), 应避免使用这些语法
二. 强烈推荐的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…
Vue2
与Vue3
中组件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()