依照MVC模式思想来解释高可复用性、低耦合性的组件定义方法
组件分类
级别从小到大:
- 基础组件:只是一种宽泛的定义,可见场景较多,与业务无关的组件。比如列表、表格、输入框等,没有实际的UI,故不能直接使用
- UI组件:与基础组件同级,为基础组件提供UI,仅仅跟样式和数据有关
- 业务组件:与基础组件是一一对应的关系,与业务相关,具有实际的UI,是能够直接使用的组件的最小单位
- 功能组件:也叫做模块组件。由多个业务组件和非组件代码组成的某个业务功能或者模块,仅仅是代码结构的一种划分,实际上没有层级的变化
- 页面组件:路由级的组件,代表某个页面
基础组件相当于MVC中的Model,UI组件相当于View,业务组件 = 基础组件 + UI组件
组件继承
基础组件是可以实现继承的,“继承”是对组件定义的一种扩展。比如轮播图组件可以继承列表组件,因为它具有列表的一些性质。
定义一个组件的文件目录如下:
![51517f7292f9b3ce0428ff125228d361.png](https://i-blog.csdnimg.cn/blog_migrate/a4b7ead63fb7b25f87597dc81e137619.png)
index.mixin.js是该基础组件的私有定义,在index.vue中通过mixin:[parentComp1, parentComp2..., selfComp]的方式实现继承。
实例
下面通过 豆瓣图书列表 的例子来定义这三个级别的组件:
基础组件定义
豆瓣图书列表这个业务功能抽象出的基础组件就是列表。 列表组件有两个prop:
- 列表数据:类型是Array,数组项为每个列表项的数据对象(列表项数目可以通过一个computed属性来表示)
- UI组件接口:每个基础组件都存在的接口,用于导入UI组件(这个prop在comp.base.mixin中定义)
- meta:用于控制UI组件的元信息(可选)
代码
components/List/index.vue
<script>
import base from '@lib/interface/comp.base.mixin' // 基础组件接口
import selfComp from './index.mixin'
export default {
name: 'List',
mixins: [base, selfComp] // 在selfComp前面加上父组件的定义mixin就可以实现继承
}
</script>
index.mixin.js
// 基础组件定义
export default {
props: {
data: {
// 列表数据
type: Array,
default: () => {
/**
* 数组项规范:
* { anymore }
*/
return []
}
}
},
computed: {
// 数据总数
dataTotal() {
return this.data.length
}
}
}
comp.base.mixin.js
// 基础组件定义Mixin
export default {
props: {
ui: {
// UI组件接口
type: Object,
default: () => {
return null
}
},
meta: {
// 用于控制UI组件的元信息
type: Object,
default: () => {
return {}
}
}
},
render: function(h) {
let uiComponent = this.ui;
if(uiComponent == null) {
return h('div', '没有定义UI组件或不能直接调用!')
}
else {
return h(uiComponent, {
props: {
scope: this, // 将基础组件的数据和方法(实例)暴露给UI组件
meta: this.meta // 通过meta属性获取控制元信息
}
})
}
}
}
UI组件定义
为List基础组件提供UI。根据业务使用场景的不同,List可以有多种UI组件,在豆瓣图书列表这个例子中,UI组件就是BookList
代码
components/List/ui/book-list.vue
<style scoped>
...
</style>
<template>
<div class="book-list">
<!-- 通过scope访问基础组件的数据和方法 -->
<div class="book-item" v-for="(item,index) in scope.data" :key="item.id">
<img class="book-cover" :src="bookCoverUrls[index]" :alt="item.name">
<div class="book-info">
<a href="javascript:void(0)">
<h3 v-text="item.name"></h3>
</a>
<p class="book-rate">
<Rate show-text allow-half disabled v-model="item.score">
<span style="color: #f5a623" v-text="item.score"></span>
</Rate>
</p>
<p v-text="item.meta"></p>
</div>
</div>
</div>
</template>
<script>
import ui from '@lib/comp.ui.mixin'
export default {
name: 'BookList',
mixins: [ui], // UI组件接口
data() {
return {
bookCoverUrls: [ ... ]
}
}
}
</script>
comp.ui.mixin.js
// UI组件定义Mixin
export default {
props: {
scope: {
type: Object,
default: () => {
return {}
}
},
meta: {
type: Object,
default: () => {
return {}
}
}
}
}
组件调用
豆瓣图书列表 = List组件 + BookList组件
<template>
<List :ui="comp.BookList" :data="bookList"/>
</template>
<script>
import List from '@c/List/index.vue'
import BookList from '@c/List/ui/book-list.vue'
export default {
components: {
List
},
data() {
return {
comp: {
BookList
},
bookList: [
{
id: 1,
name: '英国特工阿申登',
score: 4.6,
meta: '[英] 毛姆 著/理想国丨广西师范大学出版社/2019-1'
},
{
id: 2,
name: '建筑师',
score: 4.8,
meta: '(美) 大卫·马祖凯利/后浪 | 北京联合出版公司/2019-3'
},
{
id: 3,
name: '长长的回家路',
score: 4.5,
meta: '[瑞典] 弗雷德里克·巴克曼/北京联合出版社/2019-1'
},
{
id: 4,
name: '缓刑时刻',
score: 4.9,
meta: '[意]普里莫·莱维/中信出版集团/2019-1'
}
]
}
}
}
</script>
效果
![53f1491ea01e24e41f509c4c649eafb1.png](https://i-blog.csdnimg.cn/blog_migrate/92b703cdaf0a00a84e28db1b4292a20b.jpeg)