vue组件最佳实践
看了老外的一篇关于组件开发的建议(强烈建议阅读英文原版),感觉不错翻译一下加深理解。
这篇文章制定一个统一的规则来开发你的 vue 程序,以至于达到一下目的。
1. 让开发者和开发团队更容易发现一些事情。
2. 让你更好的利用你的 IDE.
3. 让你更加容易的使用打包工具
4. 让你的代码更容易碎片化以达到复用的目的。
基于模块开发
用一些功能单一的小模块来组织你的应用
Why?
对于你自己和你团队的人来说较小的模块更容易看懂 维护 复用和调试。
How?
每个组件应该保持单一 独立 可复用 可测试
把你很大的组件拆分成功能单一的小组件,尽量不让一个组件的代码超过 100 行,保持组件独立。最好是写个组件应用的小 demo
组件命名
组件命名应该遵从以下几点原则
- 有意义: 名字不要太详细,也不要太抽象。
- 短: 名字最好是 2-3 个单词。
- 可读的: 容易让人能读出来以便我们可以更容易的讨论它。
vue 组件也应该遵循以下原则
- 遵从元素命名规范 包括连字符,不要使用保留字
- 为了在其他项目中复用,应该以某个模块名字作为命名空间
Why?
- 为了让我们更好地通过名字来交流这个组件,这个组件必须 短 有意义 可读
How?
<!-- 建议这样 -->
<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。当着这些 js 很有效,但是也很复杂。因此你应该保持行内表达式简洁
Why?
- 复杂的行内表达式可读性差
- 这些行内表达式不能任意地方复用,这样会导致代码冗余
- IDE 不支持行内式 js 的语法校验
How?
把复杂的语法移动到 methods 或者计算属性中
<!-- recommended -->
<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>
<!-- avoid -->
<template>
<h1>
{{ ${(new Date()).getUTCFullYear()}-${('0' + ((new Date()).getUTCMonth()+1)).slice(-2)}
}}
</h1>
</template>
保证组件的 props 简单
尽管 vue 支持通过 props 传递复杂的 object,但是你要尽量保持 props 传递的数据简单, 尽量只传递基本数据类型 (strings, numbers, booleans)
Why?
- 简洁的 props 让你的接口 api 比较简单
- props 只传递简单类型数据和函数,让我们组件的 api 看起来更像原生 html 的属性。
- props 只传递简单类型数据,让其他开发者容易明白传什么参数。
- props 传递复杂数据类型,让你的组件很难重构,也会造成代码冗余。
How?
vue component 只传递简单数据类型或者函数如下
<!-- recommended -->
<range-slider
:values="[10, 20]"
min="0"
max="100"
step="5"
:on-slide="updateInputs"
:on-end="updateResults">
</range-slider>
<!-- avoid -->
<range-slider :config="complexConfigObject"></range-slider>
对你组件的 props 做一些限制
vue 组件中 props 就是 api, 健壮且可预测的 api 让别人更容易使用你的组件
组件的 props 通过 html 属性来编写,这些值可以是 vue 的简答字符串(:attr="value" or v-bind:attr="value")也可以不写。你应该对 props 做一些限制
Why?
对 props 做一些限制保证你的组件正常工作,即使别人没有按照你预想的方式调用你的组件。
How?
- 属性设置默认值
- 属性设置数据类型校验
- 使用组件之前检查 props 是否存在
<template>
<input type="range" v-model="value" :max="max" :min="min">
</template>
<script type="text/javascript">
export default {
props: {
max: {
type: Number, // [1*] This will validate the 'max' prop to be a Number.
default() { return 10; },
},
min: {
type: Number,
default() { return 0; },
},
value: {
type: Number,
default() { return 4; },
},
},
};
</script>
将组件设定为 this
在组件内部上下文中,this 指的是 vue 组件实例,因此在其他上下文中使用它的时候保证 'this' 在组件中可以使用
换句话说, 不要这样写 const self = this;
Why?
- 通过把
this
分配给会改变名字的组件,告诉开发者 this 是一个组件实例。
How?
组件结构
让你的组件代码按照一定的顺序编写
Why?
- 用 export 导出一个清晰的对象,提高代码可读性,同时让开发着统一代码结构
- 按一下顺序排列, 让代码容易被找到 (name; extends; props, data and computed; components; watch and methods; lifecycle methods, etc.);
- 增加
name
属性,这样再使用 vue devtools 时便于开发测试。 - 按照一定的规则写 css
- 按照如下顺序组织代码 template-script-style
How?
<template lang="html">
<div class="Ranger__Wrapper">
<!-- ... -->
</div>
</template>
<script type="text/javascript">
export default {
// Do not forget this little guy
name: 'RangeSlider',
// compose new components
extends: {},
// component properties/variables
props: {
bar: {}, // Alphabetized
foo: {},
fooBar: {},
},
// variables
data(){},
computed: {},
// when component uses other components
components: {},
// methods
watch: {},
methods: {},
// component Lifecycle hooks
beforeCreate(){},
mounted(){},
};
</script>
<style scoped>
.Ranger__Wrapper { /* ... */ }
</style>
组件事件命名
vue 提供的 vue 处理函数和表达式是严格绑定在 vm 上的。每个组件事件应该遵循一个良好的命名规范从而避免开发中出现的问题。
Why?
- 开发者任意使用事件名称会导致混乱, 例如用了原生的事件名。
- 随意命名事件会导致 dom 模板不协调。
How?
- 事件命名按照 kebab-cased(不用驼峰法) 规范 (例如 download-success)
- 一个事件名称对应唯一的事件
- 事件名应该已动词 (例如 client-api-load) 或名词 (例如 drive-upload-success) 结尾
避免使用 this.$patent
vue 支持组件嵌套,子组件获得父组件上下文,但是获得外部上下文违反了组件独立的规定,所有不要使用 this.$patent
Why?
- 就像其他组件一样 vue 组件也应该独立工作
- 如果组件依赖他的父组件那么他将难以复用。
How?
- 通过 attribute/properties 将数据从父组件传递给子组件
- 在属性表达式中把在父组件中定义的会掉函数传递到子组件
- 从子组件 emit 事件到父组件
谨慎使用this.$refs
vue 支持组件通过 this.$refs
来获得组件或者 dom 元素的上下文,大部分情况下这中用法应该被禁止。当你用他的时候也应该谨慎防止错误的组件 api。
Why?
- 就像其他组件一样,vue 的组件应该是独立的, 不能适应所有应用场景的组件是一个不好的组件
- 大部分情况下属性和事件已经足够用了
How?
- 设计好的组件 api
- 多考虑一些组件在其余业务场景下的重用
- 不要写一些特殊的代码, 如果你需要些说明你需要设计一个新的组件
- 检查是否有 props 缺失, 如果是的话补全这些缺陷。
- 检查所有的事件 (event) 大多数时候开发者只记得通过 props 实现父子组件通信吗,而忘记通过自定义事件。
- 以设计好的 api 和组件独立性为目的来更新你的组件
- 当 props 和自定义事件实在达不到目的再用
this.$refs
- 当元素不能用数据绑定或者指令操作时,用
this.$refs
是比 jquery 和document.getElement*
好一些的选择
<!-- good, no need for ref -->
<range :max="max"
:min="min"
@current-value="currentValue"
:step="1"></range>
<!-- good example of when to use this.$refs -->
<modal ref="basicModal">
<h4>Basic Modal</h4>
<button class="primary" @click="$refs.basicModal.close()">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>
<!-- avoid accessing something that could be emitted -->
<template>
<range :max="max"
:min="min"
ref="range"
:step="1"></range>
</template>
<script>
export default {
// ...
methods: {
getRangeCurrentValue() {
return this.$refs.range.currentValue;
},
},
// ...
};
</script>
使用组件名称作为 css 作用域
vue 组件的名字作为 css 根作用域类名是极好的。
Why?
- css 在 style 标签加上 scoped 能有效的防止组件内 css 污染外部组件的 css
- css 根作用域类名和组件名相同, 让开发者容易理解他们是一个组件中的。
How?
把组件名作为 css 命名空间依赖 BEM 和 OOCSS(面向对象 css)。在 style 标签上加 scoped。加了 scoped 告诉 vue 在编译时给每个类名都加一个后缀,从而避免污染其余组件或者全局样式。
<style scoped>
/* recommended */
.MyExample { }
.MyExample li { }
.MyExample__item { }
<span class="hljs-comment">/* avoid */</span>
<span class="hljs-selector-class">.My-Example</span> { } <span class="hljs-comment">/* not scoped to component or module name, not BEM compliant */</span>
</style>
为你的组件写 api 文档
一个 vue 实例通过实例化应用中的组件而来。这个实例通过组件属性配置而来。如果组件要提供给其他开发者使用,这些定制的属性也就是组件的 api 应该写在 readme.md 中。
Why?
- 文档提供给开发者一个关于组件的概要,使开发者不需要看组件的源码。这样组件比较容易让人接受和使用。
- 组件的 api 是使用组件需要配置项的指导。特别是对于那些只使用这个组件的开发者。
- 组件正式的文档告诉开发者当组件代码变化了怎么去做兼容
README.md
是一个文档应该先被阅读的。github 等代码仓库通过 README.md 来展示代码内容
How?
给一个组件增加README.md
range-slider/
├── range-slider.vue
├── range-slider.less
└── README.md
其余还包括给你的组件写小 demo,对组件做 eslint 代码审查。。