Vue风格指南总结
Vue风格指南是官方推荐的代码规范,按照指南开发大型Vue应用,可以避免项目后期出现不可预料的问题或BUG。
官方把规范定义了四个级别:A级表示必要的,B级表示强烈推荐,C级表示推荐,D级表示谨慎使用。示例代码官方给了正例和反例,我只总结了正例的写法。就像开发API一样,正确的情况通常只有一种,而错误的情况会有很多种,所以只要记住正确的就行了
A级 必要的
自定义组件在命名时,应该使用多个单词,这样可以避免未来和HTML元素冲突
Vue.component('todo-item', {
// ...
})
export default {
name: 'TodoItem',
// ...
}
组件中的data必须是一个函数,这样可以保证组件在复用时,每个组件实例都能管理自己的数据,避免数据的交叉感染
Vue.component('some-comp', {
data: function () {
return {
foo: 'bar'
}
}
})
export default {
data () {
return {
foo: 'bar'
}
}
}
// 在Vue根实例上可以直接使用对象,因为只存在一个这样的实例
new Vue({
data: {
foo: 'bar'
}
})
定义Prop应该尽量详细,至少需要指定其类型,这样能更容易的看懂组件的用法
props: {
status: String
}
// 更好的做法!
props: {
status: {
type: String,
required: true,
validator: function (value) {
return [
'syncing',
'synced',
'version-conflict',
'error'
].indexOf(value) !== -1
}
}
}
在组件上使用v-for必须和key配合,这样可以在更新 DOM 的时候,Vue 将会优化渲染,把可能的 DOM 变动降到最低
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
永远不要把 v-if 和 v-for 同时用在同一个元素上,因为这样做会导致每次条件判断,都需要重新遍历整个列表。可以把条件判断放到容器上
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
组件的样式必须设置作用域,设置作用域的方式可以是使用scoped属性、CSS Modules或者基于 class 的类似 BEM 的策略
基于class的作用域策略是更值得推荐的,因为这样外部更容易覆写组件样式
<template>
<button class="button button-close">X</button>
</template>
<!-- 使用 `scoped` 特性 -->
<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>
在插件、混入等扩展中始终为自定义的私有属性使用 $_ 前缀,并附带一个命名空间以回避和其它作者的冲突 (比如 $yourPluginName)。
var myGreatMixin = {
// ...
methods: {
$_myGreatMixin_update: function () {
// ...
}
}
}
B级 推荐
单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)。
components/
|- MyComponent.vue
components/
|- my-component.vue
基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base、App 或 V
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue
单例组件应该以 The 前缀命名,以示其唯一性
单例组件不是指只能用于一个单页面,而是指每个页面只使用一次,并且这些组件永远不接受任何 prop,因为它们是为你的应用定制的,而不是它们在你的应用中的上下文
components/
|- TheHeading.vue
|- TheSidebar.vue
和父组件紧密耦合的子组件应该以父组件名作为前缀命名,因为编辑器通常会按字母顺序组织文件,这样做可以把相关联的文件排在一起
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue
在单文件组件、字符串模板和 JSX 中没有内容的组件应该是自闭合的,但在 DOM 模板里永远不要这样做,因为HTML 并不支持自闭合的自定义元素
<!-- 在单文件组件、字符串模板和 JSX 中 -->
<MyComponent/>
<!-- 在 DOM 模板中 -->
<my-component></my-component>
在声明 prop 的时候,其命名应该始终使用 camelCase,而在模板和 JSX 中应该始终使用 kebab-case
props: {
greetingText: String
}
<WelcomeMessage greeting-text="hi"/>
JS/JSX 中的组件名应该始终是 PascalCase 的,尽管在较为简单的应用中使用 Vue.component 进行全局组件注册时,可以使用 kebab-case 字符串
Vue.component('MyComponent', {
// ...
})
Vue.component('my-component', {
// ...
})
import MyComponent from './MyComponent.vue'
export default {
name: 'MyComponent',
// ...
}
多个特性的元素应该分多行撰写,每个特性一行,因为这样更易读
<MyComponent
foo="a"
bar="b"
baz="c"
/>
C级 推荐
单文件组件应该总是让 <script>
、<template>
和 <style>
标签的顺序保持一致。且<style>
要放在最后,因为另外两个标签至少要有一个。
<!-- 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>
D级 谨慎使用
官方把D级定义为谨慎使用,表示的是这样做可能有潜在的危险,我下面直接把推荐的做法总结了一下
如果一组 v-if + v-else 的元素类型相同,最好使用 key (比如两个 <div>
元素)。
<div
v-if="error"
key="search-status"
>
错误:{{ error }}
</div>
<div
v-else
key="search-results"
>
{{ results }}
</div>
<p v-if="error">
错误:{{ error }}
</p>
<div v-else>
{{ results }}
</div>
隐性的父子组件通信
应该优先通过 prop 和事件进行父子组件之间的通信,而不是使用 this.\(parent 改变 prop。尽管this.\)parent 这种做法在很多简单的场景下可能会更方便,但这样做会牺牲数据流向的简洁性
Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: `
<input
:value="todo.text"
@input="$emit('input', $event.target.value)"
>
`
})