共计 4496 个字符,预计需要花费 12 分钟才能阅读完成。
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), | |
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({… 参数}) 更新表单数据,个别用于查问。