element-ui的el-table源码分析

5,019 阅读2分钟

1.概述

学习element-ui的源码能很好的提升vue的水平,但是element-ui中el-table的源码很复杂,并且组件的写法用的是render函数方法,而不是vue单文件,这让阅读它源码变得很困难,所以我在学习的时候,为了掌握el-table的核心思想,我做了2件事,第1件,对它的代码进行简化,删除太多细节代码.第2件,写一个html把所有代码放在html中运行

2.目标

实现下面el-table组件的使用,包含通过prop显示列,通过template自定义列

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.js"></script>
</head>
<body>
    <div id="app">
        <el-table :data="tableData">
            <el-table-column prop="name" label="姓名"></el-table-column>
            <el-table-column label="地址">
                <template slot-scope="scope">{{scope.row.address}}</template>
            </el-table-column>
        </el-table>
    </div>
</body>
<script>
var vm = new Vue({
    el: '#app',
    data: {
        tableData: [{
            name: '张三',
            address: '成都市青羊区清源路1号'
        }, {
            name: '李四',
            address: '成都市青羊区清源路2号'
        }]
    }
})
</script>

3.步骤

3.1 定义状态管理器

仿造vuex的结构,创建一个TableStore类,定义commit方法和mutations对象

// 状态管理器
class TableStore {
    constructor(){
        this.states = {
            data: null, //table的数据
            columns: [] //table的列定义
        }
    }
    commit(name, ...args){//调用mutations
        this.mutations[name].apply(this, [this.states].concat(args))
    }
}
//mutations的定义
TableStore.prototype.mutations = {
    //设置table的数据
    setData(states, data) {
        states.data = data
    },
    //插入列定义
    insertColumn(states, column) {
        states.columns.push(column)
    }
}

3.2 定义el-table组件

负责:

  • 初始化状态管理器
  • 通过默认插槽,接受table-column组件
  • 使用table-header组件和table-body组件
Vue.component('el-table',{
    template: `<div class="el-table">
        <!-- 隐藏列: slot里容纳table-column -->
        <div class="hidden-columns">
            <slot></slot>
        </div>
        <!-- 表头 -->
        <div class="el-table__header-wrapper">
            <!--表头组件-->
            <table-header :store="store"></table-header>
        </div>
        <!-- 表体 -->
        <div class="el-table__body-wrapper">
            <!--表体组件-->
            <table-body :store="store"></table-body>
        </div>
    </div>`,
    props: ['data'],//table数据
    data(){
        return {
            store: new TableStore() //状态管理器
        }
    },
    watch: {
        data: {
            immediate: true,
            handler(value) {
                // 将data添加到状态管理器中,供 table-body 使用
                this.store.commit('setData', value)
            }
        }
    }
})

3.3 定义el-table-column组件

负责:生成列定义(包含表头名称,列字段名,渲染方法),放到状态管理器中,提供给table-header组件和table-body组件使用

Vue.component('el-table-column',{
    template: `<div></div>`,
    props: ['label', 'prop'],
    computed: {
        owner() {// 寻找拥有table的外层组件
            return this.$parent;
        }
    },
    created() {  
        // 生成列定义
        let column = {
            label: this.label,//列表头显示名称
            property: this.prop,//列用到的字段名称
            renderCell: null//渲染用的方法
        };
        let renderCell = column.renderCell;
        let _self = this;
        // 生成列的渲染方法
        column.renderCell = function (createElement, data) {
            // 有插槽的情况
            if (_self.$scopedSlots.default) {
                //渲染作用域插槽
                renderCell = () => _self.$scopedSlots.default(data);
                //使用效果:
                //<template slot-scope="{row}">
                //<span>{{row.address}}</span>
                //</template>
            }else{
            // 没有插槽的情况
                renderCell = function () {
                    let { row } = data
                    let  property = column.property;
                    // 直接返回时间紧
                    return row[property]
                }
                /*实现效果:<div className="cell">张三</div>*/
            }
            //生成一个render函数
            return createElement('div', {
                'class': {
                    cell: true
                }
            },renderCell())
        }
        //生成列定义
        this.columnConfig = column
    },
    mounted(){
        // 将列定义添加到状态管理器中,供 table-body table-header 使用
        this.owner.store.commit('insertColumn', this.columnConfig)
    }
})

3.4定义table-header组件

负责根据状态管理器中列定义,渲染列表的表头

Vue.component('table-header',{
    props: ['store'],
    computed: {
        columns() { //获取状态管理器中的列定义
            return this.store.states.columns;
        }
    },
    render(createElement) { //通过createElement创建vNode
        /*
        效果:
        <table class="el-table__header">
        	<thead>
        		<th><div>姓名</div></th>
        		<th><div>地址</div></th>
        	</thead>
        </table>
        */
        return createElement('table',{class: {'el-table__header': true}}, [
            createElement('thead', 
                this.columns.map(column=>{
                    return createElement('th',[createElement('div',column.label)])
                })
            )
        ])
    }
})

3.5 定义table-body组件

负责通过状态管理器中的列定义和数据,渲染表体数据

// table-body组件
Vue.component('table-body',{
    props: ['store'],
    computed: {
        data() { //获取状态管理器中的列表数据
            return this.store.states.data;
        },
        columns() { //获取状态管理器中的列定义
            return this.store.states.columns;
        }
    },
    render(createElement) { //通过createElement创建vNode
        /*
        效果:
        <table class="el-el-table__body">
        	<tr>
        		<td>...</td>
        	</tr>
        </table>
        */
        return createElement('table',{class: {'el-el-table__body': true}}, 
            this.data.map((row)=>{
                return createElement('tr', this.columns.map(column=>{
                    return createElement('td',[column.renderCell.call(null,createElement,{row})])
                }))
            })
        )
    },
})

4 效果和完整代码

预览

5.进一步学习

vue对象的render方法学习