二次封装el-table组件

2,846 阅读1分钟

前言

平时在使用el-table的时候,经常要写el-table-column,有没有办法改造一下el-table,传入json配置表格,同时保留所有的el-table功能呢?

一、改造el-table

  1. 要实现json配置表格,首先想到的是循环el-table-column,我们先来改写一下el-table
<template>  
    <el-table  
        ref="table"  
        :border="border"  
        :data="dataSource"  
        :header-cell-style="headerCellStyle"    
        :row-key="rowKey"  
        :stripe="stripe"  
        style="width: 100%"  
        v-bind="$attrs"  
        v-on="$listeners"   
    >  
        <template #append>  
            <slot name="append" />  
        </template>  
        <wd-table-column v-for="(col, index) in columns" :key="col.key || col.prop || index" :column="col" />  
    </el-table>  
</template>  
  
<script>  
import WdTableColumn from './table-column'  
import props from './props'  
  
export default {  
    name: 'WdTable',  
    components: {  
        WdTableColumn  
    },  
    mixins: [props],
    // 向下注入scopedSlots
    provide() {  
        return {  
            scopedSlots: () => this.$scopedSlots  
        }  
    },    
    updated() {  
        this.$nextTick(() => {  
            this.$refs.table && this.doLayout()  
        })  
    }
}  
</script>

上述代码中,我们定义了一些el-table的默认Props,同时透传了$attrs和$listeners,接着我们循环了wd-table-column组件,下面我们来看wd-table-column组件实现。

<script>  
import { getValueByPath } from 'element-ui/src/utils/util'  
import WdTableColumnRender from './table-column-render'  
  
export default {  
    name: 'WdTableColumn',  
    functional: true,  
    inject: ['scopedSlots'],  
    inheritAttrs: false,  
    props: {  
        column: {  
            type: Object,  
            required: true  
        }  
    },  
    render(h, ctx) {  
        const { column } = ctx.props
        
        // 判断传入的column里有没有hidden,如果有hidden就不渲染
        if (Object.hasOwnProperty.call(column, 'hidden')) {  
            if (typeof column.hidden === 'function') {  
                if (column.hidden()) return null  
            } else {  
                if (column.hidden) return null  
            }  
        }  

        const slotScope = () => {
            // 无需自定义渲染的column types
            const NOT_CUSTOM_RENDER_TYPES = ['index', 'selection']
            // index和selection我们无需自定义渲染,所以直接跳过,保持默认渲染
            if (NOT_CUSTOM_RENDER_TYPES.includes(column.type)) return  

            return {
                // 此处如不了解,请阅读 [渲染函数 & JSX — Vue.js (vuejs.org)](https://v2.cn.vuejs.org/v2/guide/render-function.html#%E6%8F%92%E6%A7%BD)
                scopedSlots: {  
                    default: (scope) => customRender(scope),  
                    header: (scope) => customHeaderRender(scope)  
                }  
            }  
        }  
        
        // 自定义render渲染函数,使用WdTableColumnRender组件,传入props scope和render
        const columnRender = (scope, render) => h(WdTableColumnRender, {  
            props: {  
                scope,  
                render  
            }  
        })  
        
        // 自定义slot渲染,
        const columnSlot = (scope, key) => {
            // 此处的scopedSlots来自于上面provide注入的数据
            const scopedSlots = ctx.injections.scopedSlots()  
            const slotVNode = scopedSlots[key]
            // slot本身也是函数,所以直接调用即可渲染
            return typeof slotVNode === 'function' ? slotVNode(scope) : null  
        }  

        // render header
        const customHeaderRender = (scope) => {  
            if ('headerRender' in column) {  
                return columnRender(scope, column.headerRender)  
            }  
            if ('headerSlot' in column) {  
                return columnSlot(scope, column.headerSlot)  
            }  

            return column.label  
        }  
        
        // render column
        const customRender = (scope) => {  
            if ('render' in column) {  
                return columnRender(scope, column.render)  
            }  
            if ('slot' in column) {  
                return columnSlot(scope, column.slot)  
            }  
            return getValueByPath(scope.row, column.prop)  
        }  

        return h('el-table-column',  
            {  
                props: column,  
                ...slotScope()  
            },
            // 嵌套表头
            (column.children || []).map((col, index) =>  
                h('wd-table-column', {  
                    props: {  
                    column: col  
                },  
                key: col.key || col.prop || index  
            })  
        ))  
    }  
}  
</script>

上面提到了WdTableColumnRender组件,看下WdTableColumnRender实现,其实也很简单

<script>  
export default {  
    functional: true,  
    props: {  
        scope: {  
            type: Object,  
            default: () => ({})  
        },  
        render: {  
            type: Function,  
            default: () => {}  
        }  
    },  
    render: (h, ctx) => {  
        return ctx.props.render ? ctx.props.render(h, ctx.props.scope) : null  
    }  
}  
</script>

二、使用改造的el-table

<script setup>  
const columns = [  
    {  
        label: '序号',  
        type: 'index',  
        width: 70,  
        fixed: 'left'  
    },  
    {  
        label: '姓名',  
        prop: 'name',  
        width: 120  
    },  
    {  
        label: '年龄',  
        prop: 'info.age',  
        width: 100  
    },  
    {  
        label: '职业',  
        render: (_, { row }) => <el-tag>{row.job}</el-tag>,  
        width: 100  
    },  
    {  
        headerRender: () => {  
            return (  
                <div>  
                    <span style='margin-right: 10px;'>自定义表头</span>  
                    <el-button type='primary'>按钮</el-button>  
                </div>  
            )  
        },  
        render: (_, { $index }) => <span>自定义内容 - {$index}</span>  
    },  
    {  
        label: 'slot内容',  
        slot: 'content'  
    },  
    {  
        label: '此表头不显示',  
        hidden: true  
    }  
]  
  
const dataSource = [  
    {  
        name: '小二',  
        info: {  
            age: 18  
        },  
        job: '服务员'  
    },  
    {  
        name: '小三',  
        info: {  
            age: 18  
        },  
        job: '服务员'  
    }  
]  
</script>  
  
<template>  
    <wd-table :columns="columns" :data-source="dataSource">  
        <template #content="{$index}">  
            <el-link type="warning">我是slot内容 - {{ $index }}</el-link>  
        </template>  
    </wd-table>  
</template>

三、后续扩展

一个表格可能由多个区域组成,如下图,其实我们可以封装一个大的包容组件,里面每个区的内容都可以传入slot实现,从而实现解耦。每一个功能区又可以封装单个的组件,通过最外面大的包容组件去控制每一个区域的联动。

image.png

四、总结

以上就是封装el-table组件的过程,后续我们就可以用这个保留了原有组件能力的自定义的组件进行开发了,显著提升了开发效率有木有~