前端小白,请多指教。原文地址
个人感觉写 Vue 比较烦人的是写模版,比如使用 ElementUI 的 Table 组件,有多少列就要写多少个 Column,比较繁琐。而用 JS 代码则可以很灵活,庆幸的是 Vue 是支持渲染函数的,意味着可以用 JS 代码去实现组件。
首先明确一下我们的封装目标
- 对于一般的只用作显示的列,只要指定 prop 和 label 即可自动生成。
- 保留原来 Column 的 slot 能力,用户能自定义显示列。
- 自定义数据格式化器。
- 可选的控制某列是否显示。
使用和效果
先来看看最后能用这个二次封装的组件干些什么
简单数据展示
如果是单纯的数据显示,那么只要这样写
然后把需要哪些列以及数据给它即可,个人感觉是非常方便的
export default {
name: "app",
components: {
EluiDynTable,
},
data() {
return {
tableDesc: [
{ prop: "name", label: "名字" },
{ prop: "city", label: "城市" },
{ prop: "born", label: "出生时间", formatter: "ts" },
],
tableData: [
{
name: "Alice",
city: "Shanghai",
born: 946656000000,
},
{
name: "Bob",
city: "Hongkong",
born: 946699994000,
},
],
};
},
};
自定义列
如果要自定义列,比如要在最右测添加功能操作按钮,则可以这样
自定义
删除
export default {
name: "app",
components: {
EluiDynTable,
EluiDynColumn,
},
data() {
return {
tableDesc: [
{ prop: "name", label: "名字" },
{ prop: "city", label: "城市" },
{ prop: "born", label: "出生时间", formatter: "ts" },
{ prop: "operation", label: "操作", fixed: "right" },
],
tableData: [
{
name: "Alice",
city: "Shanghai",
born: 946656000000,
},
{
name: "Bob",
city: "Hongkong",
born: 946699994000,
},
],
};
},
methods: {
handleClick(row, index) {
/* eslint-disable no-console */
console.log(`deleting ${row.name} at ${index}`);
},
},
};
效果图
控制列显示
不大建议使用。调用组件的 toggle 函数,函数签名如下
toggle(prop, hidden);
prop 用来指定哪一列。如果不指定 hidden 值(boolean),则是在显示和不显示之间来回切换,如果指定则用指定值。
实现
我们首先要给各种数据类型准备一些常用的格式化器,我开箱内置里一些,具体代码查看这里:
function formatNumber(prop) {}
function formatArray(array, extra) {}
function formatTimestamp(ts) {}
function formatSecond(second) {}
function formatterByType(prop) {}
export function format({ formatter, prop, scope, extra }) {}
如果内置的格式化器不能满足要求,也是可以自定义的,给 formatter 传递custom,然后在extra里指定一个formatter,它应该是一个函数类型,具体的函数签名是这样的。
extra.formatter(prop, scope);
对于在项目中会广泛使用的格式化器则可以全局注册,提供了 API:
setFormatter(type, formatter);
然后我们对 ElColumn 进行简单的封装,default slot会默认调用格式化器对数据进行格式化展示,而header slot则会显示label值。注意把原来的 slot 再暴露出去,这样在使用的时候,就可以自定义了。
{{ h.column.label }}
{{ formatRow(scope.row[prop], scope) }}
最后使用渲染函数,实现一个 Table 组件
const EluiDynTable = {
name: "EluiDynTable",
props: {
data: {
type: Array,
default: () => [],
},
desc: {
type: Array,
default: () => [],
},
},
methods: {
toggle(prop, hidden) {
const d = this.desc.find((e) => e.prop === prop);
if (d) {
if (hidden !== undefined) {
d.hidden = !!hidden;
} else {
d.hidden = !d.hidden;
}
this.$forceUpdate();
}
},
},
render: function (h) {
const isDynColumn = (c) =>
c.componentOptions &&
c.componentOptions.Ctor.extendOptions.name === "EluiDynColumn";
const dynColumns = (this.$slots.default || []).filter(isDynColumn);
const keyOf = (c) => c.componentOptions.propsData.prop;
const columnGroups = group(dynColumns, keyOf);
const children = [];
for (let d of this.desc) {
if (d.hidden) {
continue;
}
let child = columnGroups[d.prop];
if (child) {
const propKeys = Object.keys(child.componentOptions.Ctor.options.props);
for (let k in d) {
if (propKeys.indexOf(k) >= 0) {
child.componentOptions.propsData[k] = d[k];
} else {
child.data.attrs[k] = d[k];
}
}
} else {
const propKeys = Object.keys(EluiDynColumn.props);
const props = {};
const attrs = {};
for (let k in d) {
if (propKeys.indexOf(k) >= 0) {
props[k] = d[k];
} else {
attrs[k] = d[k];
}
}
child = h(EluiDynColumn, {
props,
attrs,
});
}
children.push(child);
}
return h(Table, { props: this.$props, attrs: this.$attrs }, children);
},
};
可以看到的代码细节是,我只会筛选出default slot里的EluiDynColumn组件,认为这是自定义的列,然后遍历表格描述desc,如果用户有自定义组件,则使用自定义组件,否则自动生成 EluiDynColumn 组件。给 EluiDynColumn 的props和attrs赋值,因为 EluiDynColumn 会把 attrs 通过v-bind绑定到 ElColumn 上,因此完美保留了原来 ElColumn 的特性。还有一个细节就是 hidden 属性,这是用来控制列显示的。render 函数最后返回一个 ElTable 的 VNode 即可。