Vue.js 组件编码标准
指标
本标准提供了一种对立的编码标准来编写 Vue.js 代码。这使得代码具备如下的个性:
- 其它开发者或是团队成员更容易浏览和了解。
- IDEs 更容易了解代码,从而提供高亮、格式化等辅助性能
- 更容易应用现有的工具
- 更容易实现缓存以及代码包的分拆
目录
- 基于模块开发
- vue 组件命名
- 组件表达式简单化
- 组件 props 原子化
- 验证组件的 props
- 将
this
赋值给component
变量 - 组件结构化
- 组件事件命名
- 防止
this.$parent
- 审慎应用
this.$refs
- 应用组件名作为款式作用域空间
- 提供组件 API 文档
- 提供组件 demo
- 对组件文件进行代码校验
- 只在须要时创立组件
<!– * 应用*.vue
扩展名 –>
<!– * 为你的我的项目增加徽章标识 –>
基于模块开发
始终基于模块的形式来构建你的 app,每一个子模块只做一件事件。
Vue.js 的设计初衷就是帮忙开发者更好的开发界面模块。一个模块是应用程序中独立的一个局部。
怎么做?
每一个 Vue 组件(等同于模块)首先)必须专一于解决一个繁多的问题,独立的 、 可复用的 、 渺小的 和 可测试的。
如果你的组件做了太多的事或是变得臃肿,请将其拆分成更小的组件并放弃繁多的准则。一般来说,尽量保障每一个文件的代码行数不要超过 100 行。也请保障组件可独立的运行。比拟好的做法是减少一个独自的 demo 示例。
Vue 组件命名
组件的命名需听从以下准则:
- 有意义的: 不过于具体,也不过于形象
- 简短: 2 到 3 个单词
- 具备可读性: 以便于沟通交流
同时还须要留神:
- 必须合乎 自定义元素标准: 应用连字符分隔单词,切勿应用保留字。
app-
前缀作为命名空间: 如果十分通用的话可应用一个单词来命名,这样能够不便于其它我的项目里复用。
为什么?
- 组件是通过组件名来调用的。所以组件名必须简短、富裕含意并且具备可读性。
如何做?
<!-- 举荐 -->
<app-header></app-header>
<user-list></user-list>
<range-slider></range-slider>
<!-- 防止 -->
<btn-group></btn-group> <!-- 尽管简短然而可读性差. 应用 `button-group` 代替 -->
<ui-slider></ui-slider> <!-- ui 前缀太过于宽泛,在这里意义不明确 -->
<slider></slider> <!-- 与自定义元素标准不兼容 -->
组件表达式简单化
Vue.js 的表达式是 100% 的 Javascript 表达式。这使得其功能性很弱小,但也带来潜在的复杂性。因而,你应该尽量 放弃表达式的简单化。
为什么?
- 简单的行内表达式难以浏览。
- 行内表达式是不可能通用的,这可能会导致反复编码的问题。
- IDE 基本上不能辨认行内表达式语法,所以应用行内表达式 IDE 不能提供主动补全和语法校验性能。
怎么做?
如果你发现写了太多简单并难以浏览的行内表达式,那么能够应用 method 或是 computed 属性来代替其性能。
<!-- 举荐 -->
<template>
<h1>
{{`${year}-${month}` }}
</h1>
</template>
<script type="text/javascript">
export default {
computed: {month() {return this.twoDigits((new Date()).getUTCMonth() + 1);
},
year() {return (new Date()).getUTCFullYear();}
},
methods: {twoDigits(num) {return ('0' + num).slice(-2);
}
},
};
</script>
<!-- 防止 -->
<template>
<h1>
{{`${(new Date()).getUTCFullYear()}-${('0' + ((new Date()).getUTCMonth()+1)).slice(-2)}` }}
</h1>
</template>
组件 props 原子化
尽管 Vue.js 反对传递简单的 JavaScript 对象通过 props 属性,然而你应该尽可能的应用原始类型的数据。尽量只应用 JavaScript 原始类型(字符串、数字、布尔值)和函数。尽量避免简单的对象。
为什么?
- 使得组件 API 清晰直观。
- 只应用原始类型和函数作为 props 使得组件的 API 更靠近于 HTML(5) 原生元素。
- 其它开发者更好的了解每一个 prop 的含意、作用。
- 传递过于简单的对象使得咱们不可能分明的晓得哪些属性或办法被自定义组件应用,这使得代码难以重构和保护。
怎么做?
组件的每一个属性独自应用一个 props,并且应用函数或是原始类型的值。
<!-- 举荐 -->
<range-slider
:values="[10, 20]"
:min="0"
:max="100"
:step="5"
@on-slide="updateInputs"
@on-end="updateResults">
</range-slider>
<!-- 防止 -->
<range-slider :config="complexConfigObject"></range-slider>
验证组件的 props
在 Vue.js 中,组件的 props 即 API,一个稳固并可预测的 API 会使得你的组件更容易被其余开发者应用。
组件 props 通过自定义标签的属性来传递。属性的值能够是 Vue.js 字符串 (:attr="value"
或 v-bind:attr="value"
) 或是不传。你须要保障组件的 props 能应答不同的状况。
为什么?
验证组件 props 能够保障你的组件永远是可用的(防御性编程)。即便其余开发者并未依照你料想的办法应用时也不会出错。
怎么做?
- 提供默认值。
- 应用
type
属性校验类型。 - 应用 props 之前先查看该 prop 是否存在。
<template>
<input type="range" v-model="value" :max="max" :min="min">
</template>
<script type="text/javascript">
export default {
props: {
max: {
type: Number, // 这里增加了数字类型的校验
default() { return 10;},
},
min: {
type: Number,
default() { return 0;},
},
value: {
type: Number,
default() { return 4;},
},
},
};
</script>
将 this
赋值给 component
变量
在 Vue.js 组件上下文中,this
指向了组件实例。因而当你切换到了不同的上下文时,要确保 this
指向一个可用的 component
变量。
换句话说,如果你正在应用 ES6 的话,就不要再编写 var self = this;
这样的代码了,您能够平安地应用 Vue 组件。
为什么?
- 应用 ES6,就不再须要将
this
保留到一个变量中了。 - 一般来说,当你应用箭头函数时,会保留
this
的作用域。(译者注:箭头函数没有它本人的 this 值,箭头函数内的 this 值继承自外围作用域。) - 如果你没有应用 ES6,当然也就不会应用
箭头函数
啦,那你必须将“this”保留到到某个变量中。这是惟一的例外。
怎么做?
<script type="text/javascript">
export default {
methods: {hello() {return 'hello';},
printHello() {console.log(this.hello());
},
},
};
</script>
<!-- 防止 -->
<script type="text/javascript">
export default {
methods: {hello() {return 'hello';},
printHello() {
const self = this; // 没有必要
console.log(self.hello());
},
},
};
</script>
组件结构化
依照肯定的构造组织,使得组件便于了解。
为什么?
- 导出一个清晰、组织有序的组件,使得代码易于浏览和了解。同时也便于标准化。
- 按首字母排序 properties、data、computed、watches 和 methods 使得这些对象内的属性便于查找。
- 正当组织,使得组件易于浏览。(name; extends; props, data 和 computed; components; watch 和 methods; lifecycle methods 等)。
- 应用
name
属性。借助于 vue devtools 能够让你更不便的测试。 - 正当的 CSS 构造,如 BEM 或 rscss – 详情?。
- 应用单文件 .vue 文件格式来组件代码。
怎么做?
组件结构化
<template lang="html">
<div class="ranger-wrapper">
<!-- ... -->
</div>
</template>
<script type="text/javascript">
export default {
// 不要遗记了 name 属性
name: 'RangeSlider',
// 应用其它组件
components: {},
// 应用组件 mixins 共享通用性能
mixins: [],
// 组成新的组件
extends: {},
// 组件属性、变量
props: {bar: {}, // 按字母程序
foo: {},
fooBar: {},},
// 生命周期函数
beforeCreate() {},
mounted() {},
// 变量
data() {},
computed: {},
// 办法
watch: {},
methods: {},};
</script>
<style scoped>
.ranger-wrapper {/* ... */}
</style>
组件事件命名
Vue.js 提供的处理函数和表达式都是绑定在 ViewModel 上的,组件的每一个事件都应该依照一个好的命名标准来,这样能够防止不少的开发问题,具体可见如下 为什么。
为什么?
- 开发者能够随便给事件命名,即便是原生事件的名字,这样会带来迷惑性。
- 过于宽松的事件命名可能与 DOM 模板不兼容。
怎么做?
- 事件名也应用连字符命名。
- 一个事件的名字对应组件外的一组意义操作,如:upload-success、upload-error 以及 dropzone-upload-success、dropzone-upload-error(如果须要前缀的话)。
- 事件命名应该以动词(如 client-api-load)或是 名词(如 drive-upload-success)结尾。(出处)
防止 this.$parent
Vue.js 反对组件嵌套,并且子组件可拜访父组件的上下文。拜访组件之外的上下文违反了基于模块开发的第一准则。因而你应该尽量避免应用 this.$parent
。
为什么?
- 组件必须互相放弃独立,Vue 组件也是。如果组件须要拜访其父层的上下文就违反了该准则。
- 如果一个组件须要拜访其父组件的上下文,那么该组件将不能在其它上下文中复用。
怎么做?
- 通过 props 将值传递给子组件。
- 通过 props 传递回调函数给子组件来达到调用父组件办法的目标。
- 通过在子组件触发事件来告诉父组件。
审慎应用 this.$refs
Vue.js 反对通过 ref
属性来拜访其它组件和 HTML 元素。并通过 this.$refs
能够失去组件或 HTML 元素的上下文。在大多数状况下,通过 this.$refs
来拜访其它组件的上下文是能够防止的。在应用的的时候你须要留神防止调用了不失当的组件 API,所以应该尽量避免应用 this.$refs
。
为什么?
- 组件必须是放弃独立的,如果一个组件的 API 不可能提供所需的性能,那么这个组件在设计、实现上是有问题的。
- 组件的属性和事件必须足够的给大多数的组件应用。
怎么做?
- 提供良好的组件 API。
- 总是关注于组件自身的目标。
- 回绝定制代码。如果你在一个通用的组件外部编写特定需要的代码,那么代表这个组件的 API 不够通用,或者你可能须要一个新的组件来应答该需要。
- 查看所有的 props 是否有缺失的,如果有提一个 issue 或是欠缺这个组件。
- 查看所有的事件。子组件向父组件通信个别是通过事件来实现的,然而大多数的开发者更多的关注于 props 从漠视了这点。
- Props 向下传递,事件向上传递!。以此为指标降级你的组件,提供良好的 API 和 独立性。
- 当遇到 props 和 events 难以实现的性能时,通过
this.$refs
来实现。 - 当须要操作 DOM 无奈通过指令来做的时候可应用
this.$ref
而不是JQuery
、document.getElement*
、document.queryElement
。
<!-- 举荐,并未应用 this.$refs -->
<range :max="max"
:min="min"
@current-value="currentValue"
:step="1"></range>
<!-- 应用 this.$refs 的实用状况 -->
<modal ref="basicModal">
<h4>Basic Modal</h4>
<button class="primary" @click="$refs.basicModal.hide()">Close</button>
</modal>
<button @click="$refs.basicModal.open()">Open modal</button>
<!-- Modal component -->
<template>
<div v-show="active">
<!-- ... -->
</div>
</template>
<script>
export default {
// ...
data() {
return {active: false,};
},
methods: {open() {this.active = true;},
hide() {this.active = false;},
},
// ...
};
</script>
<!-- 如果可通过 emited 来做则防止通过 this.$refs 间接拜访 -->
<template>
<range :max="max"
:min="min"
ref="range"
:step="1"></range>
</template>
<script>
export default {
// ...
methods: {getRangeCurrentValue() {return this.$refs.range.currentValue;},
},
// ...
};
</script>
应用组件名作为款式作用域空间
Vue.js 的组件是自定义元素,这非常适合用来作为款式的根作用域空间。能够将组件名作为 CSS 类的命名空间。
为什么?
- 给款式加上作用域空间能够防止组件款式影响内部的款式。
- 放弃模块名、目录名、款式根作用域名一样,能够很好的将其关联起来,便于开发者了解。
怎么做?
应用组件名作为款式命名的前缀,可基于 BEM 或 OOCSS 范式。同时给 style 标签加上 scoped 属性。加上 scoped 属性编译后会给组件的 class 主动加上惟一的前缀从而防止款式的抵触。
<style scoped>
/* 举荐 */
.MyExample { }
.MyExample li { }
.MyExample__item { }
/* 防止 */
.My-Example { } /* 没有用组件名或模块名限度作用域, 不合乎 BEM 标准 */
</style>
提供组件 API 文档
应用 Vue.js 组件的过程中会创立 Vue 组件实例,这个实例是通过自定义属性配置的。为了便于其余开发者应用该组件,对于这些自定义属性即组件 API 应该在 README.md
文件中进行阐明。
为什么?
- 良好的文档能够让开发者比拟容易的对组件有一个整体的意识,而不必去浏览组件的源码,也更不便开发者应用。
- 组件配置属性即组件的 API,对于组件的用户来说他们更感兴趣的是 API 而不是实现原理。
- 正式的文档会通知开发者组件 API 变更以及向后的兼容性状况。
README.md
是规范的咱们应该首先浏览的文档文件。代码托管网站(GitHub、Bitbucket、Gitlab 等)会默认在仓库中展现该文件作为仓库的介绍。
怎么做?
在模块目录中增加 README.md
文件:
range-slider/
├── range-slider.vue
├── range-slider.less
└── README.md
在 README 文件中阐明模块的性能以及应用场景。对于 vue 组件来说,比拟有用的形容是组件的自定义属性即 API 的形容介绍。
Range slider
性能
range slider 组件可通过拖动的形式来设置一个给定范畴内的数值。
该模块应用 noUiSlider 来实现跨浏览器和 touch 性能的反对。
如何应用
<range-slider>
反对如下的自定义属性:
attribute | type | description |
---|---|---|
min |
Number | 可拖动的最小值. |
max |
Number | 可拖动的最大值. |
values |
Number[] optional | 蕴含最大值和最小值的数组. 如. values="[10, 20]" . Defaults to [opts.min, opts.max] . |
step |
Number optional | 减少减小的数值单位,默认为 1. |
on-slide |
Function optional | 用户拖动开始按钮或者完结按钮时的回调函数,函数承受 (values, HANDLE) 格局的参数。如:on-slide={updateInputs} , component.updateInputs = (values, HANDLE) => {const value = values[HANDLE]; } . |
on-end |
Function optional | 当用户进行拖动时触发的回调函数,函数承受 (values, HANDLE) 格局的参数。 |
如须要自定义 slider 的款式可参考 noUiSlider 文档)
提供组件 demo
增加 index.html
文件作为组件的 demo 示例,并提供不同配置状况的成果,阐明组件是如何应用的。
为什么?
- demo 能够阐明组件是独立可应用的。
- demo 能够让开发者预览组件的性能成果。
- demo 能够展现组件各种配置参数下的性能。
对组件文件进行代码校验
代码校验能够放弃代码的统一性以及追踪语法错误。.vue 文件能够通过应用 eslint-plugin-html
插件来校验代码。你能够通过 vue-cli
来开始你的我的项目,vue-cli
默认会开启代码校验性能。
为什么?
- 保障所有的开发者应用同样的编码标准。
- 更早的感知到语法错误。
怎么做?
为了校验工具可能校验 *.vue
文件,你须要将代码编写在 <script>
标签中,并使组件表达式简单化,因为校验工具无奈了解行内表达式,配置校验工具能够拜访全局变量 vue
和组件的 props
。
ESLint
ESLint 须要通过 ESLint HTML 插件来抽取组件中的代码。
通过 .eslintrc
文件来配置 ESlint,这样 IDE 能够更好的了解校验配置项:
{
"extends": "eslint:recommended",
"plugins": ["html"],
"env": {"browser": true},
"globals": {
"opts": true,
"vue": true
}
}
运行 ESLint
eslint src/**/*.vue
JSHint
JSHint 能够解析 HTML(应用 --extra-ext
命令参数)和抽取代码(应用 --extract=auto
命令参数)。
通过 .jshintrc
文件来配置 ESlint,这样 IDE 能够更好的了解校验配置项。
{
"browser": true,
"predef": ["opts", "vue"]
}
运行 JSHint
jshint --config modules/.jshintrc --extra-ext=html --extract=auto modules/
注:JSHint 不承受 vue
扩展名的文件,只反对 html
。
只在须要时创立组件
为什么?
Vue.js 是一个基于组件的框架。如果你不晓得何时创立组件可能会导致以下问题:
- 如果组件太大, 可能很难重用和保护;
- 如果组件太小,你的我的项目就会(因为深层次的嵌套而)被吞没,也更难使组件间通信;
怎么做?
- 始终记住为你的我的项目需要构建你的组件,然而你也应该尝试想到它们可能从中怀才不遇(独立于我的项目之外)。如果它们可能在你我的项目之外工作,就像一个库那样,就会使得它们更加强壮和统一。
- 尽可能早地构建你的组件总是更好的,因为这样使得你能够在一个曾经存在和稳固的组件上构建你的组件间通信(props & events)。
规定
- 首先,尽可能早地尝试构建出诸如模态框、提示框、工具条、菜单、头部等这些显著的(通用型)组件。总之,你晓得的这些组件当前肯定会在以后页面或者是全局范畴内须要。
- 第二,在每一个新的开发我的项目中,对于一整个页面或者其中的一部分,在进行开发前先尝试思考一下。如果你认为它有一部分应该是一个组件,那么就创立它吧。
- 最初,如果你不确定,那就不要。防止那些“当前可能会有用”的组件净化你的我的项目。它们可能会永远的只是(静静地)待在那里,这一点也不聪慧。留神,一旦你意识到应该这么做,最好是就把它突破,以防止与我的项目的其余局部形成兼容性和复杂性。
尽可能应用 mixins
为什么?
Mixins 封装可重用的代码,防止了反复。如果两个组件共享有雷同的性能,则能够应用 mixin。通过 mixin,你能够专一于单个组件的工作和形象的通用代码。这有助于更好地保护你的应用程序。
怎么做?
假如你有一个挪动端和桌面端的菜单组件,它们共享一些性能。咱们能够形象出这两个组件的外围性能到一个 mixin 中,例如:
const MenuMixin = {data () {
return {language: 'EN'}
},
methods: {changeLanguage () {if (this.language === 'DE') this.$set(this, 'language', 'EN')
if (this.language === 'EN') this.$set(this, 'language', 'DE')
}
}
}
export default MenuMixin
要应用 mixin,只需将其导入到两个组件中(我只展现挪动组件)。
<template>
<ul class="mobile">
<li @click="changeLanguage">Change language</li>
</ul>
</template>
<script>
import MenuMixin from './MenuMixin'
export default {mixins: [MenuMixin]
}
</script>