组件

我们在开发时经常会使用第三方组件,而这些组件的样式和功能通常又不能拿来就用,通常我们都需要对组件进行样式覆盖和二次封装才能满足需求。样式覆盖比较容易,我在这里就不再赘述啦,聊一聊我在开发时的二次封装实践。

直接封装

elementuiTable组件提供了很多功能,组件的实现原理也很巧妙,但是同一个项目中的表格基本都大同小异,这个时候可以对Table组件进行封装下,让我们可以不用关注表格的模板结构,更多时间专注到表格的内容上:

  1. 把众多个性化配置项设置为默认;
  2. TableColumn组件封装成配置式,减少template部分重复代码的编写;
  3. Pagination组件也一起封装进去;
  4. 抽象出常见的操作列,使得操作按钮可配置

大概的代码结构就是这样的:

<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

比如想要扩展 elementuiImage组件,想要增加一个放大预览的功能(官方 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 混入了原组件,如果新增的属性 previewtrue,执行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分别传递了属性,事件和插槽。完整的代码点这里

上次更新: 6/5/2020, 3:22:23 AM