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({...参数})更新表单数据,个别用于查问。