-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Vue 组件规范
- Vue组件选项的顺序
- 组件命名
- 文件夹名称
- Prop
- data
- 样式
- 模版
- 计算属性
- 指令
- 避免使用
this.$parent
- 请谨慎使用
this.$refs
- 把this赋值给component变量
- 组件事件名称
- 组件使用
Vue组件选项的顺序
export default {
el: '#app',
name: '',
// 组件类型(函数式组件-无状态(data)-无实例(this))
functional: false,
//模版修改器
delimiters: ["{{", "}}"],
comments: false,
//模版依赖
components: {},
directives: {},
filters: {},
// 组合
extends: {},
mixins: [],
// 接口
inheritAttrs: true,
model: {},
props: {},
propsData: {},
// 本地状态
data: {},
computed: {},
// 事件
watch: {},
// 生命周期
beforeCreate(){},
created(){},
beforeMount(){},
mounted(){},
beforeUpdate(){},
update(){},
activated(){},
deactiveated(){},
beforeDestroy(){},
destroyed(){ },
// 非响应式的 property
methods: {},
// 渲染
template: '',
render(){},
renderError(){}
}
组件命名
组件的命名需遵从以下原则:
有意义的
: 不过于具体,也不过于抽象简短
: 2~3单词, 保持单词完整(尽量不要简写)具有可读性
同时还需要注意:
- 必须符合自定义元素规范: 使用
kebab-case
分隔单词,切勿使用保留字
<!-- 推荐 -->
<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> <!-- 与自定义元素规范不兼容 -->
文件夹名称
- 文件名称始终保持
kebab-case
的格式. 文件包括(layout、views、utils 等)
Bad
|---views
|------Login
|------------components
|------------auth-redirect.vue
|------------index.vue
Good
|---views
|------login
|------------components
|------------auth-redirect.vue
|------------index.vue
Prop
1. Prop 定义
Prop定义应该尽量详细.
- 是组件更容易读
- 在开发环境下,传入不一样类型时会有警告,帮忙提前捕获潜在的错误来源.
Bad
props: ['status']
Good
props: {
status: String
}
2. Prop名字大小写
声明 prop 的时候,其命名应该始终使用 camelCase
(驼峰), 而在模版
和JSX
中应该始终使用 kebab-case
.
Bad
props: {
'greeting-text': String
}
<WelcomeMessage greetingText="hi"/>
Good
props: {
'greetingText': String
}
<welcome-message greeting-text="hi"></welcome-message>
3. 组件 props
原子化
虽然 Vue 支持通过属性传递复杂JavaScript对象, 但是我们应该尽可能使用原始类型数据. 尽量只使用 JavaScript 基本上类型(strings, number, booleans) 和 functions. 避免使用复杂对象.
为什么?
- 使组件API清晰直观.
- 只使用基本类型和函数作为 props 使得组件 API 更接近原生 HTML 原生元素.
- 方便其它开发更容易理解每个 prop 的含义和作用
- 当传递复杂对象时使得我们并不知道哪些属性和方法被自定义组件使用,这会使得代码难以重构和维护.
怎么做 ?
<!-- 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.js 中,组件的 props 即 API,一个稳定并可预测的 API 会使得你的组件更容易被其他开发者使用.
为什么?
验证组件 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>
data
组件的 data
必须是一个函数. 防止在组件在多例情况下 data 被共用. 例外应用root
组件可以是对象.
Bad
Vue.component('some-comp', {
data: {
foo: 'bar'
}
})
Good
Vue.component('some-comp', {
data(){
return {
foo: 'bar'
}
}
})
组件样式
为组件样式设置作用域
对于应用来说, 顶级 App
组件和布局组件中的样式可以是全局的, 但是其它所有组件应该是有作用域的.
这条规则只和单文件组件有关, 可以使用下面方式
- scoped
- CSS MOdules(基于class的类似BEM的策略)
- BEM
不管怎样,对于组件库
, 我们应该更倾向于选用基于 class的策略
而不是 scoped
attribute.
这让覆写内部样式更容易。
Bad
<template>
<button class="btn btn-close">X</button>
</template>
<style>
.btn-close {
background-color: red;
}
</style>
Good
<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
保持模版中表达式简单
组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法.
Bad
{{
fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}
Good
<!-- 在模板中 -->
{{ normalizedFullName }}
// 复杂表达式已经移入一个计算属性
computed: {
normalizedFullName: function () {
return this.fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}
}
Computed
简单的计算属性
应该把复杂计算属性分割成尽可能多的更简单的 property
.
- 易于测试
- 易于阅读
- 更好的"拥抱变化"
Bad
computed: {
price: function () {
var basePrice = this.manufactureCost / (1 - this.profitMargin)
return (
basePrice -
basePrice * (this.discountPercent || 0)
)
}
}
Good
computed: {
basePrice: function () {
return this.manufactureCost / (1 - this.profitMargin)
},
discount: function () {
return this.basePrice * (this.discountPercent || 0)
},
finalPrice: function () {
return this.basePrice - this.discount
}
}
directive
指令缩写
指令缩写(用:
表示 v-bind
、用 @
表示 v-on
和 用 #
表示v-slot:
)应该要么都用要么都不用.
Bad
<input
v-bind:value="newTodoText"
:placeholder="newTodoInstructions"
>
Good
<input
:value="newTodoText"
:placeholder="newTodoInstructions"
>
<!--or-->
<input
v-bind:value="newTodoText"
v-bind:placeholder="newTodoInstructions"
>
避免使用this.$parent
Vue.js 支持组件嵌套,并且子组件可访问父组件的上下文. 访问组件之外的上下文违反了基于模块开发的第一原则. 因此你应该尽量避免使用 this.$parent。
为什么 ?
- 组件都应该都保持独立, 如果一个组件需要访问父组件,则这规则将被打破.
- 如果一个组件需要访问它都父组件, 它就不能再在不同的上下文中被重用.
怎么做 ?
- 通过 props 将值传递给子组件中
- 通过 props 传递回调函数给子组件中调用
- 通过子组件中发射都事件来通知父组件
请谨慎使用this.$refs
Vue 支持组件通过 ref
属性去访问其它组件和HTML元素. 并通过 this.$refs
去访问组件和DOM
元素的上下文.
在大多情况,通过 this.$refs
去访问其它组件的上下文是可以避免的.
为什么 ?
- 组件都应该都保持独立, 如果一个组件不支持所有访问需求,该组件可能设计或实现有问题.
- Prop 和 Events 对于大多数组件来说应该足够了
怎么做 ?
- 创建良好组件API
- 设计组件时应该考虑组件的用途(目的)
- 不要定制代码,如果需要在通用组件中编写特定代码,这意味着它的API不够通用, 或者您可能需要一个新组件来管理其他情况.
- 检查所有的
props
是否有缺失,如果有缺失抛出问题或完善组件. - 检查所有事件, 子与父通信通常是通过事件来的(events), 大多数开发者只关注 props 而忽略这一点.
- Props 向下传递, Events 向上传递,这样父子之间是隔离的,这样尽可能保证他们都能独立运行.
- 当
props
和events
不能满足需求时, 才考虑使用this.$refs
- 当 element 不能通过数据绑定或指令方式去操作时可以使用
this.$refs
而不是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.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>
<!-- 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>
将this赋值给component变量
在 Vue.js 组件上下文中, this指向了组件实例. 因此当你切换到了不同的上下文时, 要确保 this 指向一个可用的 component 变量。
换句话说, 如果你正在使用 ES6 的话,就不要再编写 var self = this; 这样的代码了,您可以安全地使用 Vue 组件
为什么?
- 使用 ES6, 不需要把
this
保存到变量中. - 当使用箭头函数时,它词法作用则为当前上下文.
- 仅仅当不能使用 ES6 和 箭头函数时,才去考虑把
this
保存到变量中.
怎么做 ?
<script type="text/javascript">
export default {
methods: {
hello() {
return 'hello';
},
printHello() {
console.log(this.hello());
},
},
};
</script>
<!-- avoid -->
<script type="text/javascript">
export default {
methods: {
hello() {
return 'hello';
},
printHello() {
const self = this; // unnecessary
console.log(self.hello());
},
},
};
</script>
组件事件名称
每个组件事件名称应该遵循良好的名字方式,以避免的开发过程中出现问题. 原因如下:
为什么 ?
- 开发人员随意使用文件名,即使用原生的事件名称,这可能会引起混乱
- 过于宽泛的事件名称,可能会造成 DOM模版不兼容的问题
怎么做 ?
- 事件名称格式使用
kebab-cased
格式 - 事件名称应该表示当前操作,例如(
upload-error
ordropzone-upload-success
)
组件使用
1. 自闭合组件
在单文件组件
、字符串模版、JSX 中没有内容的组件应该是自闭合的 --- DOM 模版中就不要这样做.
Bad
<!-- 在单文件组件、字符串模板和 JSX 中 -->
<MyComponent></MyComponent>
<!-- 在 DOM 模板中 -->
<my-component/>
Good
<!-- 在单文件组件、字符串模板和 JSX 中 -->
<MyComponent/>
<!-- 在 DOM 模板中 -->
<my-component></my-component>
2. 模版中的组件名大小写
在单文件组件和字符串模版中组件名应该总是PascalCase
的.
DOM模版中总是 kebab-case
的.
bad
<!-- 在单文件组件和字符串模板中 -->
<mycomponent/>
<!-- 在单文件组件和字符串模板中 -->
<myComponent/>
<!-- 在 DOM 模板中 -->
<MyComponent></MyComponent>
Good
<!-- 在单文件组件和字符串模板中 -->
<MyComponent/>
<!-- 在 DOM 模板中 -->
<my-component></my-component>
// or 所有地方使用
<my-component></my-component>
3. JS/JSX中组件名大小写
JS/JSX中的组件名应该始终是PascalCase
的.
bad
Vue.component('myComponent', {
// ...
})
import myComponent from './MyComponent.vue'
export default {
name: 'myComponent',
// ...
}
export default {
name: 'my-component',
// ...
}
Good
Vue.component('MyComponent', {
// ...
})
import MyComponent from './MyComponent.vue'
export default {
name: 'MyComponent',
// ...
}
export default {
name: 'MyComponent',
// ...
}