Vue代码规范 -- 基础篇[译]

这是Vue具体代码的官方风格指南。如果你在项目中使用Vue,为了避免错误,小纠结,和反模式,这是一份很好的指导。然而,我们不相信任何风格指导对所有团队和项目是理想的,所以我们鼓励基于过去的经验,围绕着技术栈和个人的价值观做有意义的的偏差。

很大程度上,我们也总体上避免就JavaScript或HTML提出建议。我们不介意你使用分号还是逗号。我们不介意你对于HTML的属性值使用单引号还是双引号。一些特例除外,比如,我们发现某种模式在Vue的上下文是有用的。

很快,我们也将提供一些强制执行的建议。你将不得不尊重规则,但只要有可能,我们将尽可能向你展示如何使用语法检查和其他自动处理使强制行为变得简单。

最后,我们使用四类来区分我们的规则:

规则类别

优先级A:必要的

这些规则将帮助你避免错误,所以要不惜一切代价的学习和遵守他们。例外可能存在,但非常少,只有你同时精通JavaScript和Vue的专业知识才会这样做。

优先级B:强烈建议

这些规则可以提供可读性或为大多数项目的开发者经验。如果违反它们你的代码仍然可以运行,但违反它们比较少见且有正当的理由。

优先级C:被建议

当有多个同样好的选择存在,任意选择以确保一致性。在这些规则里,我们描述每一个可以接收的选项和建议一个默认选择。那意味着在你的代码里作出不同的选择会感到自由,一旦你始终如一并且有一个好的理由。请有一个好的理由去写代码!并且遵守社区标准,你将:

  1. 你可以训练你的大脑更容易的解析社区里的代码
  2. 可以复制和粘贴大多数社区代码例子而不需要修改
  3. 常常发现新的员工已经习惯了你喜欢的代码风格,至少在Vue方面是如此

优先级D: 小心使用

Vue存在的一些特性是去适应边缘情况或从历史代码库平滑合并。然而当使用不当的时候,他们可能使你的代码维护困难甚至变成bug的来源。这些规则揭露了潜在的危险性,描述了他们什么时候以及为什么应该避免。

优先级A级规则:必要的(预防错误)

多个字的组件名

组件名应该总是多个词,除了根App组件。

这个阻止冲突是存在于现在或未来的HTML元素中,由于所有的HTML元素是单个词。

1
2
3
4
5
6
7
8
Bad
Vue.component('todo',{
// ...
})
export default {
name: 'Todo',
// ...
}

1
2
3
4
5
6
7
8
Good
Vue.component('todo-item',{
// ...
})
export default {
name: 'TodoItem',
// ...
}

组件数据

组件data必须是一个函数。
当在一个组件使用data属性(任何地方除了new Vue),值必须是一个返回一个对象的函数。

详细解释
data的值是一个对象的时候,它在组件的所有实例之间共享。想象一下,例如,一个携带data的TodoList组件:

1
2
3
4
data: {
listTitle: '',
todos: []
}

我们可能想要复用这个组件,允许用户去操作多个lists(例如:购物,愿望清单,日常家务,等等)。然而有一个问题。由于每一个组件的实例引用了相同的data对象,改变一个列表的标题也会改变其他列表的标题。添加/编辑/删除操作也是如此。

替代方案是,我们想要每一个组件实例单独管理自己的data。为了实现这一点,每一个实例必须生成一个唯一的data对象。在JavaScript,这能够通过在函数中返回一个对象实现:

1
2
3
4
5
6
data: function ( ) {
return {
listTitle: ' ',
todos: [ ]
}
}

Bad

1
2
3
4
5
Vue.component('some-comp', {
data: {
foo: 'bar'
}
})

1
2
3
4
export default {
data: {
foo: bar
}

Good

1
2
3
4
5
6
7
 Vue.component('some-comp', {
data: function () {
return {
foo: 'bar'
}
}
})

1
2
3
4
5
6
7
8
// In a .vue file
export default {
data () {
return {
foo: 'bar'
}
}
}
1
2
3
4
5
6
// 在一个根Vue实例直接使用一个对象是可以的,因为一个单一的实例将永远存在
new Vue({
data: {
foo: 'bar'
}
})

Prop 定义

Prop定义应该尽可能的详细。
在提交的代码中,prop定义应该尽可能的详细,至少指定类型。

详细解释
详细的prop 定义有两个优势:

  • 他们记录组件的API,这样就很容易看出组件是如何被使用的。
  • 在开发中,如果一个组件的格式不正确,Vue将警告你,帮助你追踪潜在的错误来源。

Bad

1
2
// 只有当原型化的时候是可以的
props:['status']

Good

1
2
3
props: {
status: String
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 甚至更好
props:{
status: {
type: String,
required: true,
validator: function (value) {
return [
'syncing',
'synced',
'version-conflict',
'error'
].indexOf(value) !== -1
}
}
}

有键的v-for

总是使用带keyv-for
在组件上使用带keyv-for总是有效的,以便维护内部组件及子组件的状态。即使对元素来说,保持可预测的行为也是很好的实践,比如动画中的对象固化(object constancy)

详解解释
假如你有一个代办事项列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data: function () {
return {
todos: [
{
id: 1,
text: 'Learn to use v-for'
},
{
id: 2,
text: 'Learn to use key'
}
]
}
}

然后按字母顺序。当更新DOM时,我们将优化渲染尽可能的降低DOM改变的消耗。那可能意味着删除第一个代办元素,然后在列表的末尾再增加一个。

问题是,在这些例子中,即:更新了元素又不删除元素是重要的。例如,你可能使用<transition-group>动态的排序列表。或者渲染的元素是一个<input>维持焦点。在这些例子中,对于每一个item添加唯一的key(e.g. :key="todo.id")将告诉Vue怎么样的行为更智能。

在我们的经验当中,总是增加一个唯一的key总是更好的,你和你的团队根本不用担心边缘问题。然后在罕见的情况下,在罕见的、性能关键的场景中,对象的稳定性是不必要的,你可以有意识的破例。


Bad

1
2
3
4
5
<ul>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ul>

Good

1
2
3
4
5
6
7
<ul>
<li
v-for="todo in todos"
:key="todo.id">
{{ todo.text }}
</li>
</ul>

避免在v-for中使用v-if

不要在与v-for相同的元素上使用v-if
在两种情况下这可能是诱人的:

  • 在列表中过滤items(e.g. v-for="user in users" v-if="user.isActive")。在这些例子中,使用一个新的计算属性替换users,并返回过滤后的list(例如:activeUsers)。
  • 避免在应该隐藏列表时渲染列表(eg:v-for="user in users" v-if="shouldShowUsers").在这些例子中,移动v-if到容器元素(e.g. ul, ol)。

详细解释
当Vue处理指令的时候,v-forv-if有更高的优先级,所以这个模板:

1
2
3
4
5
6
7
8
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id">
{{ user.name }}
</li>
</ul>

将被评估类似于:

1
2
3
4
5
this.users.map(function (user) {
if (user.isActive) {
return user.name
}
})

因此即使我们仅仅为一小部分用户渲染元素,我们每次重新渲染的时候我们不得不迭代整个列表,去判断是否设置的活跃用户已经改变。

通过迭代计算属性代替,像这样:

1
2
3
4
5
6
7
computed: {
activeUsers: function () {
return this.users.filter( function (user) {
return user.isActive
})
}
}
1
2
3
4
5
6
7
<ul>
<li
v-for="user in activeUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>

我们将得到以下好处:

  • 如果users数组有改变,仅仅被过滤的列表重新计算,使过滤更高效。
  • 使用v-for="user in activeUsers",在渲染期间我们仅仅迭代被激活的元素,使渲染更高效。
  • 逻辑在表现层解耦,使维护(逻辑的改变或拓展)变得更容易。

我们从更新中得到类似的好处:

1
2
3
4
5
6
7
8
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>

到:

1
2
3
4
5
6
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id">
</li>
</ul>

通过移动v-if给一个容器元素,我们在每一个用户列表将不再检查shouldShowUsers。作为替代,我们检查一次,并且如果shouldShowUsers是false我们将不再计算。


Bad

1
2
3
4
5
6
7
8
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id">
{{ user.name }}
</li>
</ul>

1
2
3
4
5
6
7
8
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>

Good

1
2
3
4
5
6
7
<ul>
<li
v-for="user in activeUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>

1
2
3
4
5
6
7
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id">
{{ user.name }}
</li>
</ul>

组件样式作用域

对于应用,一个顶层的App组件与布局组件的样式可能是全局的,但其他组件应用有自己的作用域。
这条仅仅与单文件组件有关。scoped特性的使用不是必须的,设置作用域可以通过CSS modules实现。一个基于类的类似于BEM的策略,或其他库/惯例。

然而,组件库应该更倾向于使用基于类的策略而不是作用域属性。

这使你复写内部样式更容易,使用没有很高的差异性的可读性强的类名,而且不太会导致冲突。

详细解释
如果你正在开发一个大项目,并且和其他的开发者合作,或有时包含第三方HTML/CSS(例如:来自Auth0),设置一致的样式能够保证你的样式只适用于他们想要的组件。

scoped特性之上,使用唯一的class名称能够确保第三方CSS不会应用于你自己的HTML。例如,许多项目使用buttonbtnor icon类名,所以即使不使用类似于BEM的策略。添加特定应用或特定组件的前缀(例如:ButtonClose-icon)也能启到一些保护作用。


Bad

1
2
3
4
5
6
7
8
9
<template>
<button class="btn btn-close">X</button>
</template>

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

Good

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<button class="button button-close">X</button>
</template>

<! -- Using the `scoped` attribute -- >
<style scoped>
.button {
border: none;
border-radius: 2px;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<button :class="[$style.button, $style.buttonClose]">X</button>
</template>

<! -- 使用CSS模块 -- >
<style module>
.button {
border: none;
border-radius: 2px;
}

.buttonClose {
background-color: red;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<button class="c-Button--close">X</button>
</template>

<! -- 使用BEM惯例 -- >
<style>
.c-Button {
border: none;
border-radius: 2px;
}
.c-Button-close {
background-color: red;
}
</style>

私有属性名字

在一个插件,混入等,对于自定义属性总是使用$_前缀。然后避免和其它作者的代码冲突,也包含一个被命名的范围(例如:$_yourPluginName_)。

详细解释
Vue使用下划线_前缀去定义自己的私有属性,所以使用相同前缀(例如:_update)的风险可能会覆写一个实例属性。即使你检查了Vue当前没有使用一个特定的属性名,并不能保证在以后的升级中没有冲突。

正如$前缀,它在Vue生态系统中的目的是向用户公开的特殊的实例属性,所以在私有属性里使用它并不合适。

替代的,我们推荐把这两个前缀联合起来$_,作为一个用户自定义的私有属性不和Vue冲突的习惯用法。


Bad

1
2
3
4
5
6
7
8
var myGreatMixin = {
// ...
methods: {
update: function () {
// ...
}
}
}

1
2
3
4
5
6
7
8
9
10
var myGreatMixin = {
// ...
methods: {
methods: {
_update: function () {
// ...
}
}
}
}
1
2
3
4
5
6
7
8
var myGreatMixin = {
// ...
methods: {
$update: function () {
// ...
}
}
}
1
2
3
4
5
6
7
8
var myGreatMixin = {
// ...
methods: {
$_update: function () {
// ...
}
}
}

Good

1
2
3
4
5
6
7
8
var myGreatMixin = {
// ...
methods: {
$_myGreatMixin_update: function () {
// ...
}
}
}


原文:Style Guide for Vue(英)