Vue2风格引导|汽车之家客户端前端

avatar

原文更新于2022-07-13,由金铭翻译

github.com/vuejs/v2.vu…

  这是Vue\color{#FF3030}{Vue}特有代码的样式指南,如果你在项目中使用Vue\color{#FF3030}{Vue},这是一个不错的参考去避免错误、不确定和自动模式。然而,我们不认为任何风格指南非常适合所有的团队或项目,因此鼓励以过往经验、围绕技术栈和个人观点为基础的思想偏差。

  绝大多数情况下,我们也总体上避免为JavaScript\color{#FF3030}{JavaScript}HTML\color{#FF3030}{HTML}书写提供建议。我们不介意你是否使用冒号或尾随的逗号,我们不介意你HTML\color{#FF3030}{HTML}的属性值使用单引号或双引号。我们发现在Vue\color{#FF3030}{Vue}的一些有帮助的特别模式下,也会存在一些例外。

不久,我们也会提供在执行时的提示,有时你必须有纪律的,但是我们将尽可能试着展示给你如何去使用ESLint和其他自动处理使开发环境更简单。

最终,我们将规则分为四类:

规则类目

优先级A:必要的

  这些规则有助于避免错误,因此不惜一切代价地学习和遵守它们,也许有例外存在,但应该很少并且只有精通JavaScript\color{#FF3030}{JavaScript}Vue\color{#FF3030}{Vue}两者知识的人才可以这样做。

优先级B:强烈推荐的

  这些规则在大多数规则中提升可读性和开发体验。如果你违反了它们,你的代码也会运行,但是违反规则应该是很少的并且有理有据的。

优先级C:推荐的

  当有多种差不多好的选择存在,随意选择也可以确保一致性。在这些规则中,我们描述每个可接受的选择并且推荐一个默认的选择。这意味着在你自己的代码中,只要你坚持并且有一个好的理由,你可以自由选择做一个不同的选择。请给一个好的原因,通过接受社区的标准,你将会:
1. 训练你的大脑更容易地处理你在社区中遇到的代码;
2. 能在不需要修改的情况下复制粘贴社区的代码示例;
3. 能经常找到新的和你编码习惯相同的雇佣者,至少与Vue\color{#FF3030}{Vue}相关的。

优先级D:谨慎使用

  一些Vue\color{#FF3030}{Vue}的特征的存在是为了容纳稀少的边缘情况或者平滑地从一个旧版本迁移。然而如果过度使用,它们可能使你的代码更难维护甚至变成bug的来源。这些规则照亮潜在的危险特性,描述它们什么时候避免使用以及为什么。

1. 优先级A的规则(防止错误)

1.1 多词组件名多词组件名(必要的)

  组件名称总是多个单词,除了根组件<APP>Vue\color{#FF3030}{Vue}提供的内置组件,比如<transition><component>

  这样可以防止已经存在的和未来的HTML元素间的冲突,因为所有的HTML\color{#FF3030}{HTML}元素是一个单独的单词。
反例:

Vue.component('todo', {
  // ...
})

export default {
  name: 'Todo',
  // ...
}

好的例子:

Vue.component('todo-item', {
  // ...
})

export default {
  name: 'TodoItem',
  // ...
}

1.2 组件data(必要的)

组件的data一定是个函数

  当在一个组件中使用data\color{#FF3030}{data}属性(也就是说除了new Vue以外的任何地方),这个属性值是一个返回一个对象的函数。

详细解释

  当data值是一个对象时,它会在这个组件的所有实例之间共享。想象一下,例如,一个TodoList组件的data\color{#FF3030}{data}

data: {
  listTitle: '',
  todos: []
}

  我们也许想要复用这个组件,允许使用者去维护多个列表(例如购物清单,心愿单,日常家务等)。会产生一个问题,因为每个组件实例引用相同的data\color{#FF3030}{data}对象,改变一个清单中的标题也会改变每个清单中的标题,增删一个list时也是如此。

  相反,我们想要每一个组件的实例仅仅管理自己的data\color{#FF3030}{data} 。为此,每个实例必须生成一个独一无二的data\color{#FF3030}{data}对象,JavaScript\color{#FF3030}{JavaScript}中,可以通过返回一个对象的函数实现:
示例:

data: function () {
  return {
    listTitle: '',
    todos: []
  }
}

反例:

Vue.component('some-comp', {
  data: {
    foo: 'bar'
  }
})

export default {
  data: {
    foo: 'bar'
  }
}

好的例子:

Vue.component('some-comp', {
  data: function () {
    return {
      foo: 'bar'
    }
  }
})

// In a .vue file
export default {
  data () {
    return {
      foo: 'bar'
    }
  }
}
	
// 在根实例上直接使用对象是可以的,因为只存在一个这样的实例。
new Vue({
  data: {
    foo: 'bar'
  }
})

1.3 属性定义(必要的)

属性定义应该尽可能详细

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

详细解释
详细的属性定义有两个优点:

  • 它们记录组件的API\color{#FF3030}{API},以便于清晰地知道组件怎么去使用。
  • 开发过程中,如果一个组件提供了一个不正确的格式,我们将警告你,帮助你捕获到错误来源。

反例:

// 这样做只有开发原型时可以
props: ['status']

好的例子:

props: {
  status: String
}
// Even better!
props: {
  status: {
    type: String,
    required: true,
    validator: function (value) {
      return [
        'syncing',
        'synced',
        'version-conflict',
        'error'
      ].indexOf(value) !== -1
    }
  }
}

1.4 v-for 的key(必要的)

总是使用key伴随着v-for

  组件上必须使用key\color{#FF3030}{key}伴随vfor\color{#FF3030}{v-for},以便于维护组件内部子树的状态。甚至在元素的思想上,对于维护可预测行为,例如动画中对象恒等,这也是一个很好的方式。

详细解释
假设你有一个TODOlist:

data: function () {
  return {
    todos: [
      {
        id: 1,
        text: 'Learn to use v-for'
      },
      {
        id: 2,
        text: 'Learn to use key'
      }
    ]
  }
}

  然后你按字母排序,当更新DOM\color{#FF3030}{DOM}Vue\color{#FF3030}{Vue}将会优化渲染把变化的DOM\color{#FF3030}{DOM}降到最低。也就是可能想删除第一个TODOLIST,然后把它放入列表中的最后一项。

  问题是,一些情况下不要删除仍然保留在DOM\color{#FF3030}{DOM}中的元素。例如,你也许想使用<transition-group> 给列表加动画、或者当被渲染元素是<input>时聚焦。这些情况下,为每一项增加一个独一无二的DOM\color{#FF3030}{DOM}(例如:key ="todo.id")将会让 Vue\color{#FF3030}{Vue} 知道如何使行为更容易预测。

  依据我们的经验,最好始终添加一个唯一的key\color{#FF3030}{key},以便你和你的团队永远不必担心这些边界情况。然后在少数、性能要求高的关键场景时不是一成不变的,你可以做一些特殊处理。
反例:

<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>

1.5 避免v-if和v-for一同使用(必要的)

绝不在相同的组件中一同使用v-if 和v-for
有两个常见的场景可能我们会这样做:

  • 在列表中筛选某项(例如:使用 v-for = "user in users" v-if = "user.isActive")。在这些情况下,将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表(例如 activeUsers)。
  • 避免渲染应该被隐藏的列表(例如v-for = "user in users" v-if = "shouldShowUsers"),上述这种情况,移动v-if到容器元素上 (比如 ul、ol)。

详细解释
Vue\color{#FF3030}{Vue} 处理指令时,v-forv-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;
  }
})

  因此即使我们只渲染一小部分user元素,我们重新渲染时也要遍历整个list,无论活跃用户是否发生了变化。

  通过遍历一个计算属性代替,如下所示:

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" 之后,我们在渲染的时候只遍历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到容器元素上,我们不再检查 shouldShowUser 为列表中的每个user。相反,我们只检查一次并且当 shouldShowUser 为否的时候,我们不会运算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>

1.6 组件样式范围(必要的)

对于应用而言,顶级的App\color{#FF3030}{App} 组件和布局组件中的样式可能是全局的,但是其他样式应该是有范围的。

  它仅仅与单个组件相关,不需要都使用scope\color{#FF3030}{scope} 这个属性,可以通过CSSModules\color{#FF3030}{CSS Modules} 设置样式范围,这是一种基于class\color{#FF3030}{class} 的策略,例如BEM,也可以使用其他库或约定。
不管怎样,对于组件库,我们应该更倾向于选用基于 class 的策略而不是 scoped attribute。

  这使覆盖内部样式更容易,使用了人们可理解的 class 名称且没有太高的选择器优先级,而且不容易造成冲突。

详细解释

  如果你和其他的开发人员一起开发一个大型项目,或者有时会引入三方 HTML\color{#FF3030}{HTML}/CSS\color{#FF3030}{CSS} (例如来自Auto0),一致的作用域将会保证你的样式仅仅作用于它们想要起作用的组件。
超出scope\color{#FF3030}{scope} 属性范围,使用唯一的class\color{#FF3030}{class} 名称可以确保第三方CSS\color{#FF3030}{CSS}不会运用在你自己的 HTML\color{#FF3030}{HTML} 上。例如,许多工程使用buttonbtn或者icon组件名,即使不使用BEM这样的策略,增加一个应用特有或者组件特有的属性前缀(例如 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>

1.7 私有属性名(必要的)

  使用模块作用域使私有函数无法从外部访问。如果无法实现,始终为插件、混合等不需要考虑公共API的私有属性加上$_前缀。避免和其他作者的代码冲突,也包含了一个自己的命名范围(例如:$_yourPluginName_)。

详细解释

  Vue\color{#FF3030}{Vue} 使用_前缀定义自己的私有属性,但使用相同的前缀会面临实例中属性被重写的风险,即使你检查并确保当前没有使用这个特定的属性名,也不能保证未来版本不会出现冲突。

  至于$前缀,它在Vue\color{#FF3030}{Vue}生态中的目的是曝光给用户的特殊的实例属性,因此把它作为私有属性不是很合适。

  我们推荐合并这两个属性到_$_,作为一个用户定义的私有属性的公约,保证不会和Vue\color{#FF3030}{Vue}有冲突。
反例:

var myGreatMixin = {
  // ...
  methods: {
    update: function () {
      // ...
    }
  }
}
var myGreatMixin = {
  // ...
  methods: {
    _update: function () {
      // ...
    }
  }
}
var myGreatMixin = {
  // ...
  methods: {
    $update: function () {
      // ...
    }
  }
}
var myGreatMixin = {
  // ...
  methods: {
    $_update: function () {
      // ...
    }
  }
}

好的例子:

var myGreatMixin = {
  // ...
  methods: {
    $_myGreatMixin_update: function () {
      // ...
    }
  }
}
// Even better!
var myGreatMixin = {
  // ...
  methods: {
    publicMethod() {
      // ...
      myPrivateFunction()
    }
  }
}

function myPrivateFunction() {
  // ...
}

export default myGreatMixin

2. 优先级B的规则:强烈推荐(提高可读性)

2.1 组件文件(强烈推荐)

对于一个可用于连接文件的系统,每个组件应该有自己的文件
这帮助你快速找到你需要编辑或使用的组件。
反例:

Vue.component('TodoList', {
  // ...
})

Vue.component('TodoItem', {
  // ...
})

好的例子:

components/
|- TodoList.js
|- TodoItem.js
components/
|- TodoList.vue
|- TodoItem.vue

2.2 单文件组件名大小写(强烈推荐)

单文件组件名应该始终是PascalCase或者kebab-case之一。

  PascalCase在代码编辑器中配合自动补全的功能效果最好,因为它和我们引入的JSX或模板中的组件名尽可能一致。然而,大小写混合的文件名在不区分大小写的文件系统中可能会导致一些问题,这也是为什么kebab-case命名方式也是非常被认可。
反例:

components/
|- mycomponent.vue
components/
|- myComponent.vue

好的例子:

components/
|- MyComponent.vue
components/
|- my-component.vue

2.3 基本组件名称(强烈推荐)

详细解释
这些组件实现应用中不变的样式和行为的作用。
它们可能包含:

  • HTML\color{#FF3030}{HTML}元素
  • 其他基础组件
  • 第三方UI组件
    但是它们绝不包含全局状态(例如来自Vue Store)。
    它们的名字通常包含它们包裹元素的名字(例如BaseButton、BaseTable),除非没有它们特定功能的元素(比如BaseIcon)。如果你为一个特殊上下文构建了相似的组件,它们将几乎始终消费这些组件(例如:BaseButton也许被用作BaseSubmit)。
    这种约定的优点:
  • 编辑器按子字母顺序排序是,你的应用中的基础组件都列在一起,更容易辨认。
  • 因为组件名应该总是多个单词,这样做可以避免你在包裹简单组件时随意选择前缀 (比如 MyButtonVueButton)。

反例:

components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue

好的例子

components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue

2.4 单例组件名(强烈推荐)

只有一个活跃单例组件应该以the\color{#FF3030}{the}前缀命名、表示这可能是唯一的一个

  这不代表这个组件仅仅被使用在一个页面中,而是在一个页面中它只被用一次。这些组件绝不接收任何参数,因为它们对你的应用是特别的,而不是将它们应用在上下文中。如果你需要增加属性,那说明它是一个可重复利用的组件只是仅在每个页面中使用一次。
反例:

components/
|- Heading.vue
|- MySidebar.vue

好的例子:

components/
|- TheHeading.vue
|- TheSidebar.vue

2.5 紧耦合组件名称(强烈推荐)

子组件和父组件紧密耦合应该包含父组件的名字做一个前缀。

  如果一个组件只在其单独父组件的上下文中有意义,这种关系应该很明显体现在它的名称上。因为编辑器通常会按字母顺序排列文件,这样做可以把相关联的文件排在一起。

详细解释
你也许会通过目录中父组件嵌套子组件的方式解决这个问题,例如:

components/
|- TodoList/
   |- Item/
      |- index.vue
      |- Button.vue
   |- index.vue

components/
|- TodoList/
   |- Item/
      |- Button.vue
   |- Item.vue
|- TodoList.vue

官方不是很推荐,因为这会导致:

  • 许多文件名称相似,编辑器切换文件变得困难 。
  • 许多嵌套的子目录导致增加花费在编辑器侧边栏上的浏览时间。

反例:

components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue

好的例子:

components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue

2.6 组件名中的单词顺序(强烈推荐)

组件名称应该以高级别的单词开始,以描述性修饰词结尾。
详细解释
你也许会疑惑:
为什么我们命名时推荐使用更少的自然语言呢?
在自然英语中,形容词和其他描述性词语一致出现在名词前,否则需要使用连接词,例如:

  • Coffee with milk
  • Soup of the day
  • Visitor to the museum

  如果你想,你可以命名组件时包含这些连接词,但顺序是很重要的。

  同样要注意在你的应用中所谓的“高级别”是跟语境有关的。比如设计一个带搜索表单的应用来说,它可能包含这样的组件:

components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue

你可以注意到,很难找出哪个组件是针对搜索的,现在我们根据规则重命名这些组件:

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputExcludeGlob.vue
|- SearchInputQuery.vue
|- SettingsCheckboxLaunchOnStartup.vue
|- SettingsCheckboxTerms.vue

  因为编辑器通常通过文件首字母命名,所有组件之间的关系一目了然。

  你也可以转换成嵌套目录的方式,所有搜索的组件都嵌套到一个“search”目录下,所有设置的组件都嵌套到“setting”目录下。我们只推荐在很大的应用(例如100多组件)中使用这个方法,因为:

  • 在多级目录间导航组件,要比在单个组件目录下滚动查找要花费更多的精力。
  • 名称冲突(例如多个ButtonDelete.vue)导致很难在编辑器中快速导航到需要的特定组件的位置。
  • 重构变得更困难,因为为一个移动了的组件更新相关引用时,查找/替换通常并不高效。
    反例:
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue

好的例子:

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue

2.7 自闭合组件(强烈推荐)

在单个组件、字符串模板和JSX\color{#FF3030}{JSX}中,没有内容的组件应该是自闭合的,但是DOM\color{#FF3030}{DOM}中绝不能使用。

  自闭合组件表示它们不仅没有内容,而且刻意没有内容。其不同之处就好像书上的一页白纸和贴有“本页有意留白”标签的白纸。而且没有了不必要的闭合标签,你的代码也更简洁。

  很遗憾,HTML\color{#FF3030}{HTML}不允许自闭合元素,只有官方void\color{#FF3030}{void}元素。

  这也是为什么该策略仅适用于进入 DOM\color{#FF3030}{DOM} 之前Vue\color{#FF3030}{Vue}的模板编译器能够触达的地方,然后再产出符合DOM\color{#FF3030}{DOM}规范的HTML\color{#FF3030}{HTML}
反例:

<!-- 在单文件组件、字符串模板和 JSX 中 -->
<MyComponent></MyComponent>
<!-- 在 DOM 模板中 -->
<my-component/>

好的例子:

<!-- 在单文件组件、字符串模板和 JSX 中 -->
<MyComponent/>
<!-- 在 DOM 模板中 -->
<my-component></my-component>

2.8 模板组件名大小写(强烈推荐)

  绝大多数项目中,在单文件组件和字符串模板中,组件名应始终使用PascalCase,在DOM中使用kabab-case
PascalCasekabab-case优点更多:

  • 编辑器可以自动补全组件名,因为PascalCase也适用于 JavaScript\color{#FF3030}{JavaScript}
  • MyComponentmy-component更清楚地从一个单个单词的元素中看到,因为前者有两个字母不同而后者只有一个(一个连字符)。
  • 如果你在模板中使用任何非 Vue\color{#FF3030}{Vue} 的自定义元素,比如一个web\color{#FF3030}{web}PascalCase 确保了你的 Vue\color{#FF3030}{Vue} 组件保持清晰可见的。
    遗憾的是,由于HTML\color{#FF3030}{HTML}大小写不敏感,DOM\color{#FF3030}{DOM}模板一致使用kebab-case
    而且如果你已经是 kebab-case 的重度用户,那么与 HTML\color{#FF3030}{HTML} 保持一致的命名约定且在多个项目中保持相同的大小写规则就可能比上述优势更为重要了。在这些情况下,使用kebab-case也是可以接受的。

反例:

<!-- In single-file components and string templates -->
<mycomponent/>
<!-- In single-file components and string templates -->
<myComponent/>
<!-- In DOM templates -->
<MyComponent></MyComponent>

好的例子:

<!-- In single-file components and string templates -->
<MyComponent/>
<!-- In DOM templates -->
<my-component></my-component>
<!-- Everywhere -->
<my-component></my-component>

2.9 JS/JSX中组件命名大小写(强烈建议)

JS/JSX中组件命名应该总是PascalCase\color{#FF3030}{PascalCase} ,尽管在那些只使用全局注册的组件的简单应用中,它们可能是kebabcase\color{#FF3030}{kebab-case}

详细解释:

  在JavaScript\color{#FF3030}{JavaScript} 中,PascalCase\color{#FF3030}{PascalCase} 是类和构造函数,本质上说,是任何有清晰实例的的命名约定。Vue\color{#FF3030}{Vue}>组件也有实例,因此它也使用PascalCase\color{#FF3030}{PascalCase},额外的好处是在 JSX (和模板) 里使用 PascalCase\color{#FF3030}{PascalCase} 使得代码的读者更容易分辨 Vue 组件和 HTML 元素。
然而,对于使用全局组件明确定义的Vue.componenet,我们推荐kebab-case代替,原因是:

  • 全局组件很少被 JavaScript\color{#FF3030}{JavaScript} 引用,所以遵守 JavaScript\color{#FF3030}{JavaScript} 的约定意义不大。
  • 这些应用总包含许多DOM\color{#FF3030}{DOM} 模板,那必须使用kebab-case

反例:

Vue.component('myComponent', {
  // ...
})

import myComponent from './MyComponent.vue'
export default {
  name: 'myComponent',
  // ...
}

```  ```javascript
export default {
  name: 'my-component',
  // ...
}

export default {
  name: 'my-component',
  // ...
}

好的例子:

Vue.component('MyComponent', {
  // ...
})
Vue.component('my-component', {
  // ...
})
import MyComponent from './MyComponent.vue'
export default {
  name: 'MyComponent',
  // ...
}

2.10 完整单词的组件名(强烈推荐)

组件名相比缩写应该更倾向于全称。

  编辑器自动补全功能写长的名字的成本是非常低的,而她们提供的明确性是很宝贵的,尤其是不常见的缩写应该被避免。

反例:

components/
|- SdSettings.vue
|- UProfOpts.vue

好的例子:

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

2.11 属性名称大小写(强烈建议)

属性名称应该始终使用驼峰式声明,但是以kebab-case形式在模板和JSX中。
我们简单的遵从每个语言的约定,在 中更自然的是 驼峰式。而在 HTML\color{#FF3030}{HTML} 中是kebab-case
反例:

props: {
  'greeting-text': String
}
<WelcomeMessage greetingText="hi"/>

好的例子:

props: {
  greetingText: String
}
<WelcomeMessage greeting-text="hi"/>

2.12 多个属性的组件(强烈推荐)

有多个属性的组件应该用多行分开,每一个属性占一行。

  在JavaScript\color{#FF3030}{JavaScript} 中,用多行分隔对象的多个属性是很好的一个约定,因为提高了可读性。我们的模板和JSX\color{#FF3030}{JSX} 值得做相同的考虑。
反例:

<img src="https://vuejs.org/images/logo.png" alt="Vue Logo">
<MyComponent foo="a" bar="b" baz="c"/>

好的例子:

<img
  src="https://vuejs.org/images/logo.png"
  alt="Vue Logo"
>
<MyComponent
  foo="a"
  bar="b"
  baz="c"
/>

2.13 模板中简单的表达式(强烈建议)

组件模板应该仅仅包含简单表达式,将复杂的表达式重构成计算属性或方法。

  模板中复杂的表达式使其不清晰,我们应该努力描述出现了什么值而不是我们如果计算这个值。计算属性和方法也允许代码重用。
反例:

{{
  fullName.split(' ').map(function (word) {
    return word[0].toUpperCase() + word.slice(1)
  }).join(' ')
}}

好的例子:

<!-- In a template -->
{{ normalizedFullName }}
// The complex expression has been moved to a computed property
computed: {
  normalizedFullName: function () {
    return this.fullName.split(' ').map(function (word) {
      return word[0].toUpperCase() + word.slice(1)
    }).join(' ')
  }
}

2.14 单一计算属性(强烈推荐)

复杂属性应该尽可能被分成很多简单的属性。
详细解释:
简单说,好的计算属性名称应该是:

  • 易于测试
    当一个计算属性包含简单的表达式是,有很少的依赖,更容易去测试它工作的正确性。
  • 易读性
    简化计算属性迫使你给每个值一个描述性的名称,即使没有被重用。这使得其他开发者 (以及未来的你) 更容易专注在他们关心的代码上并搞清楚发生了什么。
  • 更适用于变化的需求
    任何可以被命名的值都能用在视图上,例如,我们可能考虑展示一条信息,告诉用户他们存了多少钱;我们可能考虑计算销售税,但也许分开展示,而不是作为最后价格的一部分。
    小的、专注的计算属性减少了信息使用时的假设性限制,需求变更时也用不着那么多重构了。

反例:

computed: {
  price: function () {
    var basePrice = this.manufactureCost / (1 - this.profitMargin)
    return (
      basePrice -
      basePrice * (this.discountPercent || 0)
    )
  }
}

好的例子:

computed: {
  basePrice: function () {
    return this.manufactureCost / (1 - this.profitMargin)
  },
  discount: function () {
    return this.basePrice * (this.discountPercent || 0)
  },
  finalPrice: function () {
    return this.basePrice - this.discount
  }
}

2.15 用引号的attribute(强烈推荐)

非空的HTML\color{#FF3030}{HTML} 属性应该始终放在引号中(在JS中单引号或双引号都可以)。

  在HTML\color{#FF3030}{HTML} 中不带空格的attribute\color{#FF3030}{attribute} 值是可以没有引号的,但这导致开发者不写空格,属性值可读性变差。
反例:

<input type=text>
<AppSidebar :style={width:sidebarWidth+'px'}>

好的例子:

<input type="text">
<AppSidebar :style="{ width: sidebarWidth + 'px' }">

2.16 指令缩写(强烈推荐)

指令缩写(: 代表 v-bind:,@ 代表v-on: , # 代表v-slot)应该同时被使用或不使用。
反例:

<input
  v-bind:value="newTodoText"
  :placeholder="newTodoInstructions"
>
<input
  v-on:input="onInput"
  @focus="onFocus"
>
<template v-slot:header>
  <h1>Here might be a page title</h1>
</template>

<template #footer>
  <p>Here's some contact info</p>
</template>

好的例子:

<input  
  :value="newTodoText"  
  :placeholder="newTodoInstructions"  
>

<input  
  v-bind:value="newTodoText"  
  v-bind:placeholder="newTodoInstructions"  
>

<input  
  @input="onInput"  
  @focus="onFocus"  
>

<input  
  v-on:input="onInput"  
  v-on:focus="onFocus"  
>

<template v-slot:header>  
  <h1>Here might be a page title</h1>  
</template>  
  
<template v-slot:footer>  
  <p>Here's some contact info</p>  
</template>

<template #header>  
  <h1>Here might be a page title</h1>  
</template>  
  
<template #footer>  
  <p>Here's some contact info</p>  
</template>

3. 优先级C的规则:推荐的(将任意选择和认知开销最小化)

3.1 组件或实例的选择顺序(推荐的)

组件/实例应该有统一的顺序。

  这是我们推荐组件选项的默认顺序,他们分成类目,因此你将知道从插件中哪个位置增加新的属性。

  1. 副作用 (触发组件外的影响)
    el
  2. 全局意识 (要求组件以外的知识)
    name
    parent
  3. 组件类型 (改变组件的类型)
    functional
  4. 模板修改器 (改变模板的编译方式)
    delimiters
    comments
  5. 模板依赖(模板中的资源使用)
    components
    directives
    filters
  6. 组合(把属性合并到选项中)
    extends
    mixins
  7. 接口(组件的接口)
    inheritAttrs
    model
    props/propsData
  8. 本地状态(本地响应式属性)
    data
    computed
  9. 事件 (通过响应式事件触发的回调)
    • watch
    • 生命周期时间(按它们被调用排序)
      beforeCreate
      created
      beforeMount
      mounted
      beforeUpdate
      updated
      activated
      deactivated
      beforeDestroy
      destroyed
  10. 非响应属性(不依赖于响应系统的实例属性)
    methods
  11. 渲染(组件输出的声明性描述)
    template/render renderError

3.2 元素属性的顺序(推荐的)

元素的属性(包括组件)应该保持一致的顺序

  这是我们为组件选项推荐的默认顺序。它们被划分为几类,因此你知道哪里去增加特有的属性和指令。

  1. 定义(提供组件的选项)
    is
  2. 列表渲染(创建多个变化的相同元素)
    v-for
  3. 条件 (元素是否渲染/显示)
    v-if
    v-else-if
    v-else
    v-show v-cloak
  4. 渲染修改(改变元素的渲染方式)
    v-pre
    v-once
  5. 全局感知 (需要超越组件的知识)
    id
  6. 唯一的 attribute (需要唯一值的 attribute)
    ref
    key
  7. 双向绑定 (把绑定和事件结合起来)
    v-model
  8. 其它 attribute (所有普通的绑定或未绑定的 attribute)
  9. 事件(组件事件监听)
    v-on
  10. 内容 (重写元素的内容)
    v-html
    v-text

3.3 组件或实例后的空行(推荐的)

你可能想在多个 property\color{#FF3030}{property} 之间增加一个空行,特别是在这些选项一屏放不下,需要滚动才能都看到的时候。

  当组件开始密集不易阅读是,多行属性之间增加空间可以使其易于浏览。例如 Vim 的编辑器,格式化后的选项还能通过键盘被快速导航。
好的例子:

props: {
  value: {
     type: String,
     required: true
  },

  focused: {
    type: Boolean,
    default: false
  },

  label: String,
    icon: String
  },

  computed: {
    formattedValue: function () {
    // ...
  },

  inputClasses: function () {
    // ...
  }
}
// 没有空行在组件易于阅读和导航时也没问题。
props: {
  value: {
    type: String,
    required: true
  },
  focused: {
    type: Boolean,
    default: false
  },
  label: String,
  icon: String
},
computed: {
  formattedValue: function () {
    // ...
  },
  inputClasses: function () {
    // ...
  }
}

3.4 单文件组件的顶级元素的顺序(推荐的)

  单文件组件总是按照 <script><template><style> 标签的顺序,最后引入<style>,因为另外两个标签至少要有一个。
反例:

<style>/* ... */</style>
<script>/* ... */</script>
<template>...</template>
<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<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>
<!-- ComponentA.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>

<!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>

4. 优先级 D 的规则:谨慎使用 (潜在的危险模式)

4.1 v-if/v-else/v-else-if 中没有key(谨慎使用)

如果一组 v-if + v-else 的元素类型相同,最好使用 key (比如两个 <div> 元素)

  默认情况下,Vue\color{#FF3030}{Vue}会尽可能高效地更新DOM\color{#FF3030}{DOM},那意味着其在相同类型的元素之间切换时,会修补已存在的元素,而不是将旧的元素移除然后在同一位置添加一个新元素。如果本不相同的元素被识别为相同,则会出现意料之外的结果。
反例:

<div v-if="error">
  错误:{{ error }}
</div>
<div v-else>
  {{ results }}
</div>

好的例子:

<div
  v-if="error"
  key="search-status"
>
 错误:{{ error }}
</div>
<div
  v-else
  key="search-results"
>
  {{ results }}
</div>

4.2 元素选择器用于带有scoped的(谨慎使用)

元素选择器应避免使用在带有scoped
scoped样式中,类选择器比元素选择器更好,因为大量使用元素选择器是很慢的。
详细解释;

  为了样式作用域化,Vue会为元素添加一个独一无二的 属性,例如 data-v-f3f3eg9。然后修改选择器,使得在匹配选择器的元素中,只有带这个 属性才会真正生效 (比如 button[data-v-f3f3eg9])

  问题在于大量的元素和 attribute 组合的选择器 (比如 button[data-v-f3f3eg9]) 会比类和属性组合的选择器 (比如 btn-close[data-v-f3f3eg9]) 慢,因此应该尽可能选用类选择器。
反例:

<template>
  <button>X</button>
</template>

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

好的例子:

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

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

4.3 隐式父子组件传值(谨慎使用)

  更倾向于使用props和事件进行父子组件传值,而不是 this.$parent 或变更 prop

  Vue\color{#FF3030}{Vue}的思想是属性向下,事件向上。遵循这一约定会让你的组件更易于理解。然而,在一些边界情况下 prop 的变更或 this.$parent 能够简化两个深度耦合的组件。*

  问题是,一些简单场景使用这些模式可能实现更简单。但不要被更简化的方式(少写代码)诱惑,而放弃了可读性( 数据状态流向更易于理解)。
反例:

Vue.component('TodoItem', {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },
  template: '<input v-model="todo.text">'
})
Vue.component('TodoItem', {
  props: {
    todo: {
      type: Object,
      required: true
  }
},
methods: {
  removeTodo () {
    var vm = this
    vm.$parent.todos = vm.$parent.todos.filter(function (todo) {
        return todo.id !== vm.todo.id
    })
  }
},
template: `
  <span>
    {{ todo.text }}
    <button @click="removeTodo">
       X
    </button>
  </span>
`
})

好的例子:

Vue.component('TodoItem', {
  props: {
        todo: {
          type: Object,
          required: true
        }
  },
template: `
    <input
      :value="todo.text"
      @input="$emit('input', $event.target.value)"
    >
  `
})
Vue.component('TodoItem', {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },
  template: `
    <span>
      {{ todo.text }}
      <button @click="$emit('delete')">
            X
      </button>
    </span>
  `
})

4.4 没有流的状态管理(谨慎使用)

优先通过 Vuex 管理全局状态,而不是通过 this.$root 或一个全局事件总线。

  通过 this.$root 和/或全局事件总线管理状态可能对于一些简单场景是方便的,但是并不适用于绝大多数的应用。

  Vuex\color{#FF3030}{Vuex}Vue\color{#FF3030}{Vue} 的官方flux-like的实现,不仅仅提供了一个管理状态的中心,还是组织、追踪和调试状态变更的工具。它很好地整合在了Vue\color{#FF3030}{Vue}生态系统(包括完整的VueDevTools\color{#FF3030}{Vue,DevTools} 支持)。
反例:

// main.js
new Vue({
  data: {
    todos: []
  },
  created: function () {
    this.$on('remove-todo', this.removeTodo)
  },
  methods: {
    removeTodo: function (todo) {
      var todoIdToRemove = todo.id
      this.todos = this.todos.filter(function (todo) {
            return todo.id !== todoIdToRemove
      })
    }
  }
})

好的例子:

// store/modules/todos.js
export default {
  state: {
    list: []
  },
  mutations: {
    REMOVE_TODO (state, todoId) {
      state.list = state.list.filter(todo => todo.id !== todoId)
    }
  },
  actions: {
    removeTodo ({ commit, state }, todo) {
      commit('REMOVE_TODO', todo.id)
    }
  }
}
<!-- TodoItem.vue -->
<template>
  <span>
    {{ todo.text }}
    <button @click="removeTodo(todo)">
      X
    </button>
  </span>
</template>

<script>
import { mapActions } from 'vuex'

export default {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },
  methods: mapActions(['removeTodo'])
}
</script>