文章目录
这里是官方的 Vue 特有代码的风格指南。如果在工程中使用 Vue,为了回避错误、小纠结和反模式,该指南是份不错的参考。不过我们也不确信风格指南的所有内容对于所有的团队或工程都是理想的。所以根据过去的经验、周围的技术栈、个人价值观做出有意义的偏差是可取的。
对于其绝大部分,我们也总体上避免就 JavaScript 或 HTML 的本身提出建议。我们不介意你是否使用分号或结尾的逗号。我们不介意你在 HTML 属性中使用单引号还是双引号。不过当我们发现在 Vue 的情景下有帮助的特定模式时,也会存在例外。
**不久之后,我们还会提供操作层面的技巧。**有的时候你只需要遵守规则,而我们会尽可能向你展示如何使用 ESLint 及其它自动化程序把操作层面弄得更简单。
最终,我们把所有的规则归为了四个大类:
1. 规则归类
1.1 优先级 A:必要的
这些规则会帮你规避错误,所以学习并接受它们带来的全部代价吧。这里面可能存在例外,但应该非常少,且只有你同时精通 JavaScript 和 Vue 才可以这样做。
1.2 优先级 B:强烈推荐
这些规则能够在绝大多数工程中改善可读性和开发体验。即使你违反了,代码还是能照常运行,但例外应该尽可能少且有合理的理由。
1.3 优先级 C:推荐
当存在多个同样好的选项,选任意一个都可以确保一致性。在这些规则里,我们描述了每个选项并建议一个默认的选择。也就是说只要保持一致且理由充分,你可以随意在你的代码库中做出不同的选择。请务必给出一个好的理由!通过接受社区的标准,你将会:
- 训练你的大脑,以便更容易的处理你在社区遇到的代码;
- 不做修改就可以直接复制粘贴社区的代码示例;
- 能够经常招聘到和你编码习惯相同的新人,至少跟 Vue 相关的东西是这样的。
1.4 优先级 D:谨慎使用
有些 Vue 特性的存在是为了照顾极端情况或帮助老代码的平稳迁移。当被过度使用时,这些特性会让你的代码难于维护甚至变成 bug 的来源。这些规则是为了给有潜在风险的特性敲个警钟,并说明它们什么时候不应该使用以及为什么。
2. 优先级 A 的规则:必要的 (规避错误)
2.1 组件名为多个单词👿
组件名应该始终是多个单词的,根组件 App
以及 <transition>
、<component>
之类的 Vue 内置组件除外。
这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的。
❌ 反例
// 反例 1
Vue.component('todo', {
// ...
})
// 反例 2
export default {
name: 'Todo',
// ...
}
✔️ 好例子
// 好例子 1
Vue.component('todo-item', {
// ...
})
// 好例子 2
export default {
name: 'TodoItem',
// ...
}
2.2 组件数据👿
**组件的 data
必须是一个函数。**当在组件中使用 data
属性的时候 (除了 new Vue
外的任何地方),它的值必须是返回一个对象的函数。
当 data
的值是一个对象时,它会在这个组件的所有实例之间共享。想象一下,假如一个 TodoList
组件的数据是这样的:
data: {
listTitle: '',
todos: []
}
我们可能希望重用这个组件,允许用户维护多个列表 (比如分为购物、心愿单、日常事务等)。这时就会产生问题。因为每个组件的实例都引用了相同的数据对象,更改其中一个列表的标题就会改变其它每一个列表的标题。增删改一个待办事项的时候也是如此。
取而代之的是,我们希望每个组件实例都管理其自己的数据。为了做到这一点,每个实例必须生成一个独立的数据对象。在 JavaScript 中,在一个函数中返回这个对象就可以了:
data: function () {
return {
listTitle: '',
todos: []
}
}
❌ 反例
// 反例1
Vue.component('some-comp', {
data: {
foo: 'bar'
}
})
// 反例2
export default {
data: {
foo: 'bar'
}
}
✔️ 好例子
// 好例子 1
Vue.component('some-comp', {
data: function () {
return {
foo: 'bar'
}
}
})
// 好例子 2
// In a .vue file
export default {
data () {
return {
foo: 'bar'
}
}
}
// 好例子 3
// 在一个 Vue 的根实例上直接使用对象是可以的,
// 因为只存在一个这样的实例。
new Vue({
data: {
foo: 'bar'
}
})
2.3 Prop 定义👿
**Prop 定义应该尽量详细。**在你提交的代码中,prop 的定义应该尽量详细,至少需要指定其类型。
细致的 prop 定义有两个好处:
- 它们写明了组件的 API,所以很容易看懂组件的用法;
- 在开发环境下,如果向一个组件提供格式不正确的
prop
,Vue 将会告警,以帮助你捕获潜在的错误来源。
❌ 反例
// 这样做只有开发原型系统时可以接受
props: ['status']
✔️ 好例子
// 好例子 1
props: {
status: String
}
// 好例子 2
// 更好的做法!
props: {
status: {
type: String,
required: true,
validator: function (value) {
return [
'syncing',
'synced',
'version-conflict',
'error'
].indexOf(value) !== -1
}
}
}
2.4 为 v-for 设置键值👿
**总是用 key
配合 v-for
。**在组件上总是必须用 key
配合 v-for
,以便维护内部组件及其子树的状态。甚至在元素上维护可预测的行为,比如动画中的对象固化 (object constancy),也是一种好的做法。
假设你有一个待办事项列表:
data: function () {
return {
todos: [
{
id: 1,
text: '学习使用 v-for'
},
{
id: 2,
text: '学习使用 key'
}
]
}
}
然后你把它们按照字母顺序排序。在更新 DOM 的时候,Vue 将会优化渲染把可能的 DOM 变更降到最低。即可能删掉第一个待办事项元素,然后把它重新加回到列表的最末尾。
这里的问题在于,不要删除仍然会留在 DOM 中的元素。比如你想使用 <transition-group>
给列表加过渡动画,或想在被渲染元素是 <input>
时保持聚焦。在这些情况下,为每一个项目添加一个唯一的键值 (比如 :key="todo.id"
) 将会让 Vue 知道如何使行为更容易预测。
根据我们的经验,最好始终添加一个唯一的键值,以便你和你的团队永远不必担心这些极端情况。也在少数对性能有严格要求的情况下,为了避免对象固化,你可以刻意做一些非常规的处理。
❌ 反例
<ul>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ul>
✔️ 好例子
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
2.5 避免 v-if 和 v-for 用在一起
**永远不要把 v-if
和 v-for
同时用在同一个元素上。**一般我们在两种常见的情况下会倾向于这样做:
- 为了过滤一个列表中的项目 (比如
v-for="user in users" v-if="user.isActive"
)。在这种情形下,请将users
替换为一个计算属性 (比如activeUsers
),让其返回过滤后的列表。 - 为了避免渲染本应该被隐藏的列表 (比如
v-for="user in users" v-if="shouldShowUsers"
)。这种情形下,请将v-if
移动至容器元素上 (比如ul
、ol
)。
当 Vue 处理指令时,v-for
比 v-if
具有更高的优先级,所以这个模板:
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
将会经过如下运算:
this.users.map(function (user) {
if (user.isActive) {
return user.name
}
})
因此哪怕我们只渲染出一小部分用户的元素,也得在每次重渲染的时候遍历整个列表,不论活跃用户是否发生了变化。
通过将其更换为在如下的一个计算属性上遍历:
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
我们将会获得如下好处:
- 过滤后的列表只会在
users
数组发生相关变化时才被重新运算,过滤更高效。 - 使用
v-for="user in activeUsers"
之后,我们在渲染的时候只遍历活跃用户,渲染更高效。 - 解耦渲染层的逻辑,可维护性 (对逻辑的更改和扩展) 更强。
为了获得同样的好处,我们也可以把:
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
更新为:
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
通过将 v-if
移动到容器元素,我们不会再对列表中的每个用户检查 shouldShowUsers
。取而代之的是,我们只检查它一次,且不会在 shouldShowUsers
为否的时候运算 v-for
。
❌ 反例
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
✔️ 好例子
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
对于应用来说,顶级 App
组件和布局组件中的样式可以是全局的,但是其它所有组件都应该是有作用域的。
这条规则只和单文件组件有关。你不一定要使用 scoped
attribute。设置作用域也可以通过 CSS Modules,那是一个基于 class 的类似 BEM 的策略,当然你也可以使用其它的库或约定。
不管怎样,对于组件库,我们应该更倾向于选用基于 class 的策略而不是 scoped
attribute。
这让覆写内部样式更容易:使用了常人可理解的 class 名称且没有太高的选择器优先级,而且不太会导致冲突。
如果你和其他开发者一起开发一个大型工程,或有时引入三方 HTML / CSS (比如来自 Auth0),设置一致的作用域会确保你的样式只会运用在它们想要作用的组件上。
不止要使用 scoped
attribute,使用唯一的 class 名可以帮你确保那些三方库的 CSS 不会运用在你自己的 HTML 上。比如许多工程都使用了 button
、btn
或 icon
class 名,所以即便你不使用类似 BEM 的策略,添加一个 app 专属或组件专属的前缀 (比如 ButtonClose-icon
) 也可以提供很多保护。
❌ 反例
<template>
<button class="btn btn-close">X</button>
</template>
<style>
.btn-close {
background-color: red;
}
</style>
✔️ 好例子
<template>
<button class="button button-close">X</button>
</template>
<!-- 使用 `scoped` attribute -->
<style scoped>
.button {
border: none;
border-radius: 2px;
}
.button-close {
background-color: red;
}
</style>
<template>
<button :class="[$style.button, $style.buttonClose]">X</button>
</template>
<!-- 使用 CSS Modules -->
<style module>
.button {
border: none;
border-radius: 2px;
}
.buttonClose {
background-color: red;
}
</style>
<template>
<button class="c-Button c-Button--close">X</button>
</template>
<!-- 使用 BEM 约定 -->
<style>
.c-Button {
border: none;
border-radius: 2px;
}
.c-Button--close {
background-color: red;
}
</style>