基于element-ui实现table可配置化

15,451 阅读4分钟

写在前面

感谢 饿了么前端团队提供组件化框架elememt-ui,本文基础组件使用element-ui。

大背景

在开发一些系统过程中,使用table作数据展示在所难免。先来看看el-table组件。

非常简单易用的组件,根据提供的data数据,配置table每一列的数据和label。没错,这样似乎都是ok的,但是在开发大量包含table的页面,会发现每次都需要去复制el-table-column,或者你会说你已经很属性element-ui整套框架的文档。然而,重复的代码很多及代码量很大。作为一名程序员,某一天需要写自己已经写过的某段代码,是不是感觉有点不应该呢。。。

数据驱动

<el-table
  :data="tabledata">
   <el-table-column
    v-for="(item,key) in columnsConfig"
    :key="key"
    :label="item.label"
    :prop="item.prop">
    </el-table-column>
</el-table>
data () {
  return {
    columnsConfig: [{
      prop: 'logicCategoryId',
      label: '编号(ID)'
    }, {
      prop: 'name',
      label: '分类名称'
    }]
  }
}

这样可以很轻松的渲染出来table。

这里有个小技巧

el-table-column改造下

<el-table
  :data="tabledata">
   <el-table-column
    v-for="(item,key) in columnsConfig"
    :key="key"
    v-bind="item">// 这里改造
    </el-table-column>
</el-table>

v-bind可以绑定一个对象,这样会读取item对象的每个属性,使用到组件当中去。比如增加width: 100,只需要在columnsConfig没一项增加width即可,el-table-column不需要改动。

使用slots怎么办

<el-table-column
  label="日期"
  width="120">
  <template scope="scope">{{ scope.row.date }}</template>
</el-table-column>

很明显,el-table-column的slots配置在columnsConfig数组不能用。 在源码找到解决办法(文件路径:node_modules_element-ui@1.3.7@element-ui\packages\table\src\table-column.js),create钩子函数代码如下:

在this.columnConfig.renderCell函数渲染每一个table cell,如果使用slots会读取,slots中的内容。 编写一个新组件使用vue extends,继承el-table-columns,重写了renderCell函数改变其返回内容,这时候用jsx将内容用return slots的函数传回来。

首先,看看columnsConfig怎么写

columnsConfig: [{
  cellType: 'slots',// 不是所有的列都需要重写,加个字段标记下
  prop: 'logicCategoryId',
  label: '编号(ID)',
  renderCell: (scope) => {
    return (
      <el-button type="text" on-click={() => that.viewGoods(scope.row)}>
        { scope.row.productCount }
      </el-button>
    )
  }
}, {
  prop: 'name',
  label: '分类名称'
}],

其次,开发新组件 column-plus.vue,代码如下

<script>
import { TableColumn } from 'element-ui'
// renderCell 函数,类型可扩展
const renderCell = {
  slots: function (h, data) {
    // 接受传入的renderCell函数
    let renderCell = () => {
      return this.renderCell ? this.renderCell(data) : ''
    }
    return <div class="cell">{ renderCell(h, data) }</div>
  }
}
export default {
  extends: TableColumn,// 继承el-table-column
  props: {
    prop: {
      type: [String, Number]
    },
    cellType: {
      type: String,
      validator: function (value) {
        let valid = ['text', 'input', 'slots'].includes(value)
        !valid && console.error(`columnPlus组件不适配 ${value} 类型`)
        return valid
      }
    },
    renderCell: {
      type: Function
    }
  },
  // el-table-column 先调用在调用本身的
  created () {
    if (renderCell[this.cellType]) {
      this.columnConfig.renderCell = renderCell[this.cellType].bind(this)
    }
  }
}
</script>

最后,组件用通过模板,配置数据即可

<el-table
  :data="tabledata">
   <column-plus v-bind="item" v-for="(item,key) in columnConfigs" :key="key">
  </column-plus>
</el-table>

配置数据

columnsConfig: [{
  cellType: 'slots',
  prop: 'logicCategoryId',
  label: '编号(ID)',
  renderCell: (scope) => {
    return (
      <el-button type="text" on-click={() => that.viewGoods(scope.row)}>
        { scope.row.productCount }
      </el-button>
    )
  }
}, {
  prop: 'name',
  label: '分类名称'
}]****

写在后面

组件开发实现可配置开发,可达到快速开发的目的和简化代码。 需要说明vue extends和mixins属性,这两个属性都是继承,属性和方法重写,钩子函数是先调用父类在调用自己的,上面的例子(el-table-column 先调用在调用本身的),但是区别是:

  • extends单继承,同时使用优先级高
  • mixins多继承,同时使用优先级低

另外,vue-cli使用jsx编写需要安装三个插件。

  • babel-helper-vue-jsx-merge-props
  • babel-plugin-syntax-jsx
  • babel-plugin-transform-vue-jsx

补充

发布了一天没想到有这么小伙伴阅读,有几点需要补充下。

评论区 @hold 评论更iview如出一辙,后来去翻看iview的文档确实,一直都没用iview开发过,不知道table也是用render来实现的,确实分享之后自己学到了更多,感谢@hold。

这是iview,table组件的代码

render: (h, params) => {
    return h('div', [
        h('Button', {
            props: {
                type: 'text',
                size: 'small'
            }
        }, 'View'),
        h('Button', {
            props: {
                type: 'text',
                size: 'small'
            }
        }, 'Edit')
    ]);
}

当然使用render之后,需要使用filter,就比较尴尬了。不过,vue的filter其实也是语法糖,最终编译是编译成函数处理。

那如果想使用filter怎么办?

data () {
    return {
      columns: [{
        renderCell: (scope) => {
          return (
              { that.stateTxt(scope.row.state)}
          )
        }
      }]
    }
    },
    methods: {
        stateTxt (val) {}
    }

写过react都知道是这么干的。

所以,一直以来前端把js写在html中,还是把html写js中,都是争论不休的话题,如何选择还开发自己拿捏。

不使用render代码是这样的

<template>
    <el-table
      :data="tableData"
      style="width: 100%">
      <el-table-column
        prop="date"
        label="日期"
        width="180">
      </el-table-column>
      <el-table-column
        prop="name"
        label="姓名"
        width="180">
      </el-table-column>
      <el-table-column
        prop="address"
        label="地址">
      </el-table-column>
    </el-table>
  </template>

使用render处理是这样的

<el-table
  :data="tabledata">
   <column-plus v-bind="item" v-for="(item,key) in columnConfigs" :key="key">
  </column-plus>
</el-table>

data () {
    return {
      columnConfigs: [{
        label: '日期',
        width: '180',
        prop: 'date'
      }, {
        label: '姓名',
        width: '180',
        prop: 'name'
      }, {
        label: '地址',
        prop: 'address'
      }]
    }
}