vue现在使用非常广泛,对于一些公用的功能我们通常也会封装成组件,同时还有各类的UI组件库给我们开发提供了便利。
为什么要封装成组件
- 能够把页面抽象成多个相对独立的模块
- 实现代码重用,提高开发效率和代码质量,使得代码易于维护
为什么要讲基于第三方UI库封装组件
这段时间经手了几个项目,都是后台管理系统的,大家知道后台管理系统最多的就是table以及表单,几乎90%的页面都会包含这两个,但从中发现了好几个问题:
第一,有些项目表格基本没有做封装,直接是使用的element-ui或者iview等UI库提供现成的copy过来的,然后很多页面可以看到的就是表格、表单的各种逻辑,而且每个页面基本代码都相似,不同的就是调用的接口,表单的配置。
第二,虽然有些项目对表格或者表单进行了二次封装,但是维护起来却很不方便,参数都是通过一个一个传递过来,代码量大,并且slot也不支持,大大降低了灵活性。
下面以antd-vue封装一个Table组件为例进行一个简单的封装实例。
封装前考虑的问题?
如何将自己定义的组件 可以支持库中当前组件所有接收参数。
如果通过参数一个个传 第一个参数太多,第二个 如果第三方库有更新增加了接受参数,自己库也需要进行更新。
- 如何将所有传过来的值 挂载到UI库的组件上。
- 如何支持插槽。
如果能解决以上的三个问题,其实也就完成50%了,剩下的就是处理一些逻辑。
我们先来解决第一个问题。
下面是antd官方的一个实例:
<template>
<a-table :columns="columns" :data-source="data">
<a slot="name" slot-scope="text">{{ text }}</a>
</a-table>
</template>
<script>
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
scopedSlots: { customRender: 'name' },
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
width: 80,
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
tags: ['nice', 'developer'],
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 2 Lake Park, London No. 2 Lake Park',
tags: ['loser'],
},
];
export default {
data() {
return {
data,
columns,
};
},
};
</script>
现在我要改成可以如下方式使用并且效果一样,tablePanel是我需要封装的组件。
<template>
<TablePanel :columns="columns" :data-source="data">
<a slot="name" slot-scope="text">{{ text }}</a>
</TablePanel>
</template>
<script>
const columns = [
// ...此处省略 同上
];
const data = [
// ...此处省略 同上
];
export default {
data() {
return {
data,
columns,
};
},
};
</script>
现在上方代码只传入了两个参数,官方文档是有15+个API参数可以配置的,如果我们一个个传进去,然后又通过props一个个接口这样特别繁琐,而且当UI库进行更新增加了参数时,组件又需要同步修改。
其实每个组件都有一个props对象,我们只要把子组件的props赋给当前组件的props就可以继承子组件的所有属性了。
TablePanel组件:
<template>
<a-table :columns="columns" :data-source="data" />
</template>
<script>
import {Table} from 'ant-design-vue'
export default {
props: Object.assign({}, Table.props) // 通过合并对象后赋值给props
}
</script>
接下来我们处理第二个和第三个问题,想要解决这个两个问题,我们可以通过jsx来处理。
首先我们需要安装 Babel 插件
npm install @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props
然后添加预设到 babel.config.js
:
module.exports = {
presets: ['@vue/babel-preset-jsx'],
}
然后修改TablePanel组件:
<script>
import {Table} from 'ant-design-vue'
export default {
props: Object.assign({}, Table.props),
render() {
return <a-table {...{ props: this.$props, scopedSlots: this.$scopedSlots }} />
}
}
</script>
这样我们就能将所有的props属性传递给Table组件,并支持插槽。可以按如下方式使用,参数以及插槽使用同antd,Table组件相同。
<template>
<TablePanel :columns="columns" :data-source="data" ...其他antd,table组件支持的参数>
<a slot="name" slot-scope="text">{{ text }}</a>
</TablePanel>
</template>
<script>
const columns = [
// ...此处省略 同上
];
const data = [
// ...此处省略 同上
];
import TablePanel from 'components/TablePanel'
export default {
components: {
TablePanel
},
data() {
return {
data,
columns,
};
},
};
</script>
剩下的我们可以开始封装分页请求的逻辑,完整代码如下:
<script>
import T from 'ant-design-vue/es/table/Table' // 此处引用的table组件实现的JS文件,也可以通过 import {Table} from 'ant-design-vue';
/**
* 表格组件
* @function loadTableData: 获取表格数据
* @param{function} tableRequest: 请求table数据的Promise方法
* @param{Object} tableParams: 表格请求数据需要额外带的参数
* @see {@link https://www.antdv.com/components/table-cn/#API} 继承所有antd表格API参数
*/
export default {
props: Object.assign({}, T.props, {
tableRequest: {
type: Function,
default: () => {},
},
tableParams: {
type: Object,
default: null,
},
joinLoad: {
type: Boolean,
default: true,
},
}),
data() {
return {
tableData: [],
localPagination: {},
pageSize: 10,
page: 1,
localLoading: false,
params: {},
}
},
methods: {
/**
* 请求table数据
* @param{{page: number, page_size: number}} options: 请求参数
* @returns {Promise<void>}
*/
async loadTableData(options = {}) {
if (Object.keys(options).length > 0) {
this.params = options
}
const params = {
size: this.pageSize,
page: this.page,
...this.params,
...this.tableParams,
}
this.localLoading = true
const { data } = await this.tableRequest(params)
if (data.code === 200) {
this.tableData =
data.result.list.map((value, index) => {
return {
...value,
key: value.key || index,
}
}) || []
if (Object.prototype.toString.call(data.result) === '[object Object]') {
this.localPagination = {
total: data.result.total_count,
current: data.result.page,
showTotal: total => {
return `共 ${total} 条`
},
}
}
}
this.localLoading = false
},
/**
* 表格切换页数
* @param {{current: string|number, pageSize: string|number}} pagination
* current: 当前页
* pageSize:当前显示页数
*/
_onChange(pagination) {
if (this.params.page) {
delete this.params.page
}
this.page = pagination.current
this.pageSize = pagination.pageSize
this.loadTableData()
},
},
mounted() {
this.joinLoad && this.loadTableData()
},
render() {
let props = {
...this.$props,
dataSource: this.tableData,
loading: this.localLoading,
pagination: this.localPagination,
}
return (
<a-table
{...{ props, scopedSlots: this.$scopedSlots }}
onChange={this._onChange}
onExpand={(expanded, record) => {
this.$emit('expand', expanded, record)
}}
/>
)
},
}
</script>
父组件可以通过this.$ref['tablePanel].loadTableData({...参数})更新表单数据,一般用于查询。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。