组件化开发为的前端开发带来了许多的便利,组件的功能包括:代码复用,组织代码, 便于测试,容易重构等等。Vue的组件系统支持多种代码复用的方案,拥有完整的组件生态,帮助开发人员快速的开发模块化的代码,本文主要介绍Vue组件的基础知识点以及如何设计一个优良的组件便于复用和维护。
做个广告:欢迎大家访问我们持续维护的React组件库react.kingdee.design/
一. Vue组件的构成
Vue组件是可复用的 Vue 实例,其组成如下图:
二. Vue 组件声明
1.字符串模板
Vue.component('button-counter', {
data: function () { return { count: 0 } },
template: 'You clicked me {{ count }} times.'
})
2.单文件组件是指文件扩展名为.vue 文件,支持局部组件,语法高亮,css与处理器
<template>
<div class="example">{{ msg }}</div>
</template>
<script>
export default {
data () {
return {
msg: 'Hello world!'
}
}
}
</script>
<style>
.example {
color: red;
}
</style>
3.渲染函数
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement( 'h' + this.level, // 标签名称
this.$slots.default // 子节点数组
)},
props: {
level: {
type: Number,
required: true
}
}})
4.JSX
import AnchoredHeading from './AnchoredHeading.vue'
new Vue({
el: '#demo',
render: function (h) {
return (
<AnchoredHeading level={1}>
<span>Hello</span> world!
</AnchoredHeading>
)
}
})
5.vue-class-component 相关资料
<template>
<div>
<input v-model="msg">
<p>prop: {{propMessage}}</p>
<p>msg: {{msg}}</p>
<p>helloMsg: {{helloMsg}}</p>
<p>computed msg: {{computedMsg}}</p>
<button @click="greet">Greet</button>
</div>
</template>
<script>
import Vue from 'vue'
import Component from 'vue-class-component'
@Component({
props: {
propMessage: String
}
})
export default class App extends Vue {
// initial data
msg = 123// use prop values for initial data
helloMsg = 'Hello, ' + this.propMessage
// lifecycle hook
mounted () {
this.greet()
}
// computedget computedMsg () {return 'computed ' + this.msg
}
// method
greet () {
alert('greeting: ' + this.msg)
}
}
</script>
三. 组件分类
1.按组件注册方式: 全局组件/局部组件/单文件组件(前文有介绍)
2.动态组件
官方文档: cn.vuejs.org/v2/guide/co…
<component :is="currentTabComponent"></component>
在多个组件可在同一位置切换的时候,可以使用动态组件,减少v-if/v-else 在页面中出现的次数。使得代码更容易理解,例如在tab页切换的场景中可以使用, 参考示例 。如果希望保持每个组件的状态,可以加上keep-alive.
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
3.异步组件 官方文档
4.函数式组件
vue函数式组件可以由以下两种方式声明:
<template functional>
<span>{{ format(date) }}</span>
</template>
Vue.component('my-component', {
functional: true, // Props 是可选的
props: { // ... }, // 为了弥补缺少的实例 // 提供第二个参数作为上下文
render: function (createElement, context) { // ...
}})
函数式组件有以下几个特征:
- 没有管理自己的状态(data属性)
- 没有监听传给它的状态(props)
- 没有生命周期函数 这也意味着它是一个无状态组件, 无实例组件(没有this),所以函数式组件没有vue响应式系统的开销,可以提升应用的性能。函数式组件应用上主要有两种,第一种是在遇到组件只是接收props而纯粹展示用,下面代码片段来自于vue-admin-template 。
<script>
export default {
name: 'MenuItem',
functional: true,
props: {
icon: {
type: String,
default: ''
},
title: {
type: String,
default: ''
}
},
render(h, context) {
const { icon, title } = context.props
const vnodes = []
if (icon) {
vnodes.push(<svg-icon icon-class={icon}/>)
}
if (title) {
vnodes.push(<span slot='title'>{(title)}</span>)
}
return vnodes
}
}
</script>
第二种是为子组件提供额外的功能,例如:element-ui中为menu提供transition效果的el-menu-collapse-transition, 或者我们常用的vue-router当中的router-view也是函数式组件。
5.按组件功能: 结构式组件/样式型组件
结构式组件是一种描述组件大体结构的例如如下代码:
<templete>
<kux-header></kux-header>
<kux-body></kux-body>
<kux-footer></kux-footer>
</templete>
...
该组件只是描述了页面的整体结构,而样式型组件相对来说容易理解。结构型组件和样式型组件并不是非此即彼,但是在开发过程中越是清晰的组件职责,越是容易理解,以及后续维护。
6.组合型组件/配置型组件
这两个组件类型常见于基础组件库的开发中,其中组合型组件:以下代码来自于element-ui的选择框组件
<el-select v-model="value" placeholder="请选择">
<el-option value=“1”>1</el-option>
<el-option value="2">2</el-option>
</el-select>
...
我们也可以通过另外一种方案来实现该组件: 对外并不暴露el-option组件,只需要在el-select中传递options属性,el-select内部循环渲染el-option即可。
<templete>
<el-select v-model="value" :options="options" placeholder="请选择"> </el-select>
</templete>
<script>
export default {
data () {
return {
options: [{value: 1, text: 1}, {value: 2, text: 2}]
}
}
}
</script>
...
组合型组件在antd和element-ui 的开发过程中都大量使用,具有清晰的内部结构,能够看到具体的内部逻辑,但是组件间的业务联系比较少,ref 引用又相互隔离,适合基础基础库的开发。而配置型组件在我们平时业务开发中使用的比较多,它可以实现比较复杂的交互组件。
7.按组件的组织结构: 基础组件/ 业务组件 / 页面组件
在我们搭建开发脚手架的时候,组件间的按照功能来进行隔离也很重要,每种组价放在单独的目录下方便快速定位,增强组件的语义理解,在安排人员开发中也能减少一些冲突。
8.受控组件/非受控组件
受控组件还是非受控组件概念来于react中.文档地址。是指组件的状态是来自于Dom节点还是react的状态中。一个常用的例子:
非受控组件:
<input type="text" ref={this.input} />
受控组件:
<input type="text" value={this.state.value} onChange={this.handleChange} />
handleChange(event) {
this.setState({value: event.target.value});
}
...
非受控组件中想要获得input 的值要通过ref引用也就是说input的值是存储于Dom中,而在受控组件中input 的值会通过this.handleChange同步到this.state.value中。所以受控与否其实讲的是是否收到react本身的控制。对于Vue开发来说,我们并不常遇到类似问题,大多数情况下我们都会使用官方提供的v-model指令来将input的状态通过到组件内。也就是说我们用的就是受控组件的形式去处理表单中的input。
<input type="text" v-model="name" />
而受控组件和非受控组件不仅仅用于表单中,我们平时的业务开发也经常会遇到,例如一个文章组件
<templete>
<article-body :content="article.content"></article-body>
<comment :comments="article.comments"></comment>
</templete>
非受控组件comment的实现(相对于父组件来说是非受控组件,它本身由于v-model的存在是受控组件)如下:
<templete>
<comment-item v-for="item in comments">{{item.content}}</comment-item>
<div>
<input v-model="commentContent" />
<button @click="commit">提交评论</button>
</div>
</templete>
<script>
export defalut {
data () {
return {
commentContent: ''
}
},
methods: {
commit () {
Axios.post(url, {content: this.commentContent})
}
}
}
</script>
其中comment组件实现了两种功能,一种是评论展示功能,一种是发表评论的功能。上文中的comment组件实现方案就是非受控型组件,它自己实现保持了commentContent评论内容,并且在自己内部进行提交。我们还有另外一种受控型组件的实现方案:
<templete>
<comment-item v-for="item in comments">{{item.content}}</comment-item>
<div>
<input v-model="commentContent" />
<button @click="commit">提交评论</button>
</div>
</templete>
<script>
export defalut {
data () {
return {
commentContent: ''
}
},
methods: {
commit () {
this.$emit('commit', this.commentContent) // 提交到父组件去处理
}
}
}
</script>
所以这个地方受控的说法可以理解为是否受到父组件的控制。总的来说受控组件的行为受到外部驱动,非受控组件的行为完全由自己控制,在使用方面非受控组件维持了自己的状态,但是开发人员往往忘记状态的同步。而受控组件可能需要提供很多额外的接口给其他组件使用。
9.有状态组件/无状态组件
有状态组件和无状态组件主要的区别在与组件本身是否管理了自己的状态。无状态组件主要适合用来定义模板,而有状态组件适合定义交互和业务逻辑。 无状态组件(通过具名插槽扩展组件):
<templete>
<div class="base-manage-box dst" >
<div class="dst-bg alert-z-index"></div>
<div class="dst-body alert-z-index">
<div class="dst-header">
<slot name="header"></slot>
</div>
<div class="dst-content">
<slot name="content"></slot>
</div>
<div class="dst-footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</templete>
10. 无渲染组件(renderless compontent)
vue 无渲染组件不渲染任何html到dom,而是提供可复用的状态和javascript逻辑 示例代码
Renderless.vue
<script>
export default {
render() {
return this.$scopedSlots.default({ name: 'John' });
}
};
</script>
App.vue
<template>
<renderless v-slot="{ name }">
<p>{{ name }}</p>
</renderless>
</template>
<script>
import Renderless from './Renderless.vue';
export default {
components: {
Renderless,
}
};
</script>
其中 Renderless.vue 只提供属性名name='John'。另外更多内容可参考 参考文章
11.高阶组件
相对于上面提供的几种组件,高阶的组件意义就显得很小,因为在vue实现高阶组件本身就很复杂,而且也不实用。想要深入了解可参考。
12.总结
组件的分类并不是停留在学术意义上的一种认知,而是在实际开发中我们都会遇到的问题,假如一个组件,它是基础组件还是业务组件,它需要展示样式还是描述页面架构,它是有状态的还是无状态的,如果是无状态的是否可以设置为函数式组件以提高性能,等等。对组件清晰的分类更能在让我们在开发过程中思路更清晰,整体工程结构更合理。文中有问题的欢迎指正。另外下一篇文章敬请期待。