组件
我们在开发时经常会使用第三方组件,而这些组件的样式和功能通常又不能拿来就用,通常我们都需要对组件进行样式覆盖和二次封装才能满足需求。样式覆盖比较容易,我在这里就不再赘述啦,聊一聊我在开发时的二次封装实践。
直接封装
elementui
的 Table
组件提供了很多功能,组件的实现原理也很巧妙,但是同一个项目中的表格基本都大同小异,这个时候可以对Table
组件进行封装下,让我们可以不用关注表格的模板结构,更多时间专注到表格的内容上:
- 把众多个性化配置项设置为默认;
- 把
TableColumn
组件封装成配置式,减少template
部分重复代码的编写; - 把
Pagination
组件也一起封装进去; - 抽象出常见的操作列,使得操作按钮可配置
大概的代码结构就是这样的:
<template lang="pug">
div
el-table(:data="tableData")
el-table-column(v-for="col in tableColumns" :props="col.props" :label="col.label")
div(v-if="tableData.length > 0")
el-pagination
</template>
具体实现可以看这里
组件扩展
直接封装最大的问题是封装后的组件把原有组件的功能都屏蔽了,虽然v-bind="$attrs" v-on="$listeners"
可以把属性和事件都传递个原有组件,但是slot
就显得无能为力了。
模板语法虽好,但不太灵活的问题就凸显出来了,换个角度,其实我们可以利用 VNode 数据对象向子组件传递插槽内容。不熟悉的同学可以再看看官网的 渲染函数 render 一章节的介绍,JSX 中也有支持 slots
比如想要扩展 elementui
的 Image
组件,想要增加一个放大预览的功能(官方 2.11 已经支持了)
大概的代码结构:
import { Image, Dialog } from 'element-ui'
import jsx from './mixins/jsx'
export default {
name: 'el-image',
mixins: [Image, jsx],
components: { eleImage: Image, Dialog },
props: {
preview: {
type: Boolean,
default: false
}
},
data() {
return {
dialogVisible: false
}
},
methods: {
renderPreview() {
return (
<div>
<ele-image {...this.jsx_inherit()} />
<dialog {...this.jsx_attrSync('visible', 'dialogVisible')}>
<img width="100%" src={this.src} />
</dialog>
</div>
)
}
},
render(h) {
let ret
if (this.preview) {
ret = this.renderPreview()
} else {
ret = Image.render.call(this, h)
}
return ret
}
}
直接利用 mixin 混入了原组件,如果新增的属性 preview
为true
,执行renderPreview
方法,这个方法中调用了两个有关 JSX 的方法,其中 jsx_attrSync
实现的是模板语法中的 属性修饰符 :key.sync="variable"
以及jsx_inherit
方法,也是整个组件继承中最重要的部分,但其实很简单:
const jsx_inherit = (defaultOption = {}) => {
return {
attrs: {
...this.$props,
...this.$attrs,
...defaultOption.attrs,
// 实际传的值
...this.$options.propsData
},
on: {
...defaultOption.on,
...this.$listeners
},
scopedSlots: this.$scopedSlots
}
}
attrs
,on
,scopedSlots
分别传递了属性,事件和插槽。完整的代码点这里
← vnode