- 优先使用 ES6/7 语法
- 不使用
$broadcast
与$dispatch
- 尽量使用单向绑定
- 数据初始化
- Watch 的范围应尽量小
- 使用 .vue 文件编写组件
- 自定义元素的标签名应包含连字符
- 合理使用内置元素的内置属性
- 可复用组件应定义清晰的公开接口
- 模板的顶级元素始终是单个元素
- Props 是只读的
- 避免使用函数作为组件 Props
- 使用简写语法
- 遵循组合优于继承的原则
- 使用 normalizr 优化数据
- 与社区偏好保持一致,可以更好地利用社区资源
- 必要时使用 babel 或 rollup 转成 ES5 兼容的语法
// deprecated:
var foo = 'foo'
// avoid:
let bar = 'bar'
// avoid:
const foo = 'foo',
bar = 'bar',
baz = 'baz'
// recommended:
const foo = 'foo'
const bar = 'bar'
const baz = 'baz'
- 跨多级传递数据会降低可维护性,应尽量避免(多级数据传递,可考虑采用 Vuex 进行统一的数据中心管理)
- vue@2.x 已经不支持 $broadcast 与 $dispatch方法,基于 vue@1.x 的项目,应考虑未来可以更平缓地升级
- 必要时使用逐级向上
$emit
来实现$dispatch
的效果
- 虽然 Vue 支持双向绑定,但应尽量使用单向绑定,让数据流易于理解
- 父组件通过属性(
props
)向子组件传递数据,子组件通过事件($emit
)向父组件传递数据变化
- 明确定义的数据模型更加适合 Vue 的数据观察模式
- 建议在定义组件时,在 data 选项中初始化所有已知的需要进行动态观察的属性
为什么要这样做呢?因为 Vue 是通过递归遍历初始数据中的所有属性,并用 Object.defineProperty 把它们转化为 getter 和 setter 来实现数据观察的。如果一个属性在实例创建时不存在于初始数据中,那么 Vue 就没有办法观察这个属性了。
例如,给定下面的模板:
<div id="demo">
<p :class="green: validation.valid">{{message}}</p>
<input v-model="message">
</div>
// avoid:
new Vue({
el: '#demo',
data: {}
})
// recommended:
new Vue({
el: '#demo',
data: {
message: '',
validation: {
valid: false
}
}
})
- 更直接,更明确,以及更好地利用 Vue 的响应系统
// .vue
export default {
data () {
return {
a: {
b: {
c: 1
}
}
}
},
ready () {
this.a.b.c = 2
// or
this.a = {
b: {
c: 2
}
}
},
watch: {
'a.b.c': function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
// -> new: 2, old: 1
}
}
}
- 除必要的 v-for/v-if 外,数据逻辑应尽量写在 script 里,保持 template 逻辑简单
- 组件样式文件应使用外链,方便自定义皮肤
- 因为浏览器内置的 HTML 元素标签名,都不含有连字符(
-
),这样可以做到有效区分。参考
- 自定义元素的自定义属性应避免占用内置元素的内置属性
假设有 c-button.vue 组件实现对 button 元素的封装,输出:
<button class="primary" type="submit">...</button>
如下,使用 type 属性定义 button 的风格(此处使用 class 实现,类似 bootstrap),使用 htmlType 属性定义 button 的 type 属性,容易引起混淆:
<!-- avoid: -->
<!-- <c-button type="primary" htmlType="submit"></c-button> -->
<template>
<button :class="type" :type="htmlType"><slot></slot></button>
</template>
<script>
export default {
props: ['type', 'htmlType']
}
</script>
建议,沿用 button 的 type 属性,避免不必要的误解:
<!-- recommended: -->
<!-- <c-button buttonStyle="primary" type="submit"></c-button> -->
<template>
<button :class="buttonStyle" :type="type"><slot></slot></button>
</template>
<script>
export default {
props: ['type', 'buttonStyle']
}
</script>
- 在编写组件时,记住是否要复用组件有好处。一次性组件跟其它组件紧密耦合没关系,但是可复用组件应当定义清晰的公开接口:
- props:允许外部环境传递数据给组件;
- events:允许组件触发外部环境的 action;
- slots:允许外部环境插入内容到组件的视图结构内。
<!-- recommended: -->
<template>
<button :class="['c-button', className]"
:type="type" @click="$emit('click')"><slot></slot></button>
</template>
<script>
export default {
props: ['className', 'type']
}
</script>
- 在使用 template 选项时,模板的内容将替换实例的挂载元素。因而推荐模板的顶级元素始终是单个元素。
<!-- avoid: -->
<template>
<div>root node 1</div>
<div>root node 2</div>
</template>
<!-- recommended: -->
<template>
<div>
I have a single root node!
<div>node 1</div>
<div>node 2</div>
</div>
</template>
- 与 React 的 props 一样,应该坚持 Vue 组件的 Props 是只读的,Props 的修改只能是自上而下的,即从父组件发起
假设有组件 my-comp.vue
// my-comp.vue
// avoid:
export default {
props: ['a', 'b'],
methods: {
someMethod () {
this.a = 'new value for a'
}
}
}
// recommended:
export default {
props: ['a', 'b'],
methods: {
someMethod () {
// emit that a is changed
this.$emit('change', {
key: 'a',
value: 'new value for a'
})
}
}
}
所以正确的做法是在父组件中监听变化并响应
<!-- recommended: -->
<template>
<my-comp :a="value" @change="mutate"></my-comp>
</template>
<script>
import MyComp from 'my-comp.vue'
export default {
data () {
return {
value: 'original value for a'
}
},
methods: {
mutate ({key, value}) {
if (key === 'a') {
// update the value for prop a
this.value = value
}
}
}
components: {
MyComp
}
}
</script>
- Props 应该用来传递简单的数据类型
- 避免将函数作为 Props,比如设计对话框时,不应将点击回调通过 Props 来实现,而应该通过事件来实现
- 总是使用简写语法,使用
:attr
替代v-bind:attr
,使用@event
替代v-on:event
<!-- recommended: -->
<my-component
:foo="baz"
:bar="qux"
@event-a="doThis"
@event-b="doThat">
<!-- content -->
<img slot="icon" src="...">
<p slot="main-text">Hello!</p>
</my-component>
- 组件的复用,遵循组合优于继承的原则,使代码复用更具弹性、更易于扩展。
- 对于大型项目,应考虑使用 normalizr 优化数据,或简单地使用
utils/normalizer