阅读 427

【组件】用原生JS封装一个Table组件

写在前面

在写项目的时候,我们经常会用一些组件,比如:模态框、表格、分页等。组件的应用大大减少了项目的开发成本,同时也提高了代码的质量等。所以,封装组件成为了每个人的必须拥有的技能。本篇文章将使用原生JS封装一个Table组件。

组件封装

实现目标

antd中的Table组件为目标,实现以下功能:

  • width值控制表格宽度。

  • columnsdataSource中的数据相对应,没有对应数据的地方显示为空。

  • 实现columns中的render函数方法。

  • columns数据中key值的检查,没有或者重复,给出警告。

  • columnswidth可设置列宽

  • columnsalign值控制内容在表格中所处的位置。

搭建结构

首先,先搭建一下代码的结构

window.Table(function(){
    class Table {
        constructor(options) {
            // 获取所需要数据
            this.columns = options.columns;
            this.dataSource = options.dataSource;
            this.width = options.width;

            this.init();   // 调用初始化函数
        } 

        // 初始化
        init() {

        }
    }

    return function proxy(options = {}{
        options = Object.assign({
            columns: [],
            dataSource: [],
            width'80%',
        }, options);
        return new Table(options)
    }
})()
复制代码

接下来,我们来做一个异常处理

1.如果options不为对象、columnsdataSource不为数组,则报错

if(!Array.isArray(options?.columns)) {
    throw new Error('error:columns must be a array');
}
if(!Array.isArray(options?.dataSource)) {
    throw new Error('error:dataSource must be a array');
}
if(options === null || typeof options !== "object") {
    throw new Error('error:options must be a object');
}
复制代码

2.当columnskey没有或者重复时,给出警告

if(!Array.isArray(options?.columns)) {
    throw new Error('error:columns must be a array');
else {
    for(let i = 0; i < options?.columns.length; i++) {
        for(let j = i + 1; j < options?.columns.length; j++) {
            if(!options?.columns[i]?.key) {
                console.error('warning:Each item in columns should have a key');
                break;
            } else {
                if(options?.columns[i]?.key === options?.columns[j]?.key) {
                    console.error('warning:The key for each item in columns should be unique');
                    break;
                }
            }
        }
    }
}
复制代码

既然是一个组件,那么就应该有对应的UI样式,所以接下来这一步,需要创建节点

init() {
    this.createElement();
}
// 创建dom节点函数,两个参数: 1.节点类型 2.css样式
create(type, cssText) {
    let ele = document.createElement(type);
    ele.style.cssText = cssText;
    return ele;
}
createElement() {

}
复制代码

创建tabletheadtbody

createElement() {
    // table
    this.$TABLE = this.create('table', `
        width: ${this.width};
    `);
    // thead
    this.$TABLE_HEAD = this.createHead();
    // tbody
    this.$TABLE_BODY = this.createBody();

    // 向table中添加thead
    this.$TABLE.appendChild(this.$TABLE_HEAD);

    // 向body中插入table
    document.body.appendChild(this.$TABLE);
}
复制代码

创建thead函数

createHead() {
    let { columns } = this;
    let THEAD_TH =  null;
    // 创建thead
    this.$THEAD = this.create("thead", `background: #e3e3e3`); 
    // 创建tr
    this.$THEAD_TR = this.create("tr");                         
    // 遍历创建th,并且向th中添加内容
    for(let i = 0; i< columns.length; i++) {
        THEAD_TH  = this.create("th", `
                        border: 1px solid #999;
                        width: ${item?.width};
                    `)
        THEAD_TH.innerHTML = columns[i].title;
    }
    // 遍历向tr中添加th节点
    for(let j = 0; j < THEAD_TH; j++) {
        this.$THEAD_TR.appendChild(THEAD_TH.appendChild(THEAD_TH[j]));
    }
    // 向thead中添加tr节点
    this.$THEAD.appendChild(this.$THEAD_TR);
    // 返回thead
    return this.$THEAD;
}
复制代码

现在的样式

创建tbody函数

createBody() {
    let { dataSource, columns } = this;
    // 初始化数据
    let TBODY_TR = null,
        TBODY_TD = null,
        TBODY = this.create("tbody");     

    // 遍历生成tbody节点下的tr和td
    for(let i = 0; i < dataSource.length;i++) {
        // 创建tr
        TBODY_TR = this.create('tr');     
        for(let j = 0;j < columns.length;j++) {
            // 创建td
            TBODY_TD = this.create('td'`
                border: 1px solid #999;
                text-align: ${columns[j]?.align};
            `
);
            // 传入的render是函数
            if(columns[j]?.render && typeof columns[j]?.render === "function") {
                // 执行render函数,传入行值和列值,并且获得返回值
                let render = columns[j]?.render(dataSource[i][columns[j]?.dataIndex], dataSource[i]);
                // 如果返回值是一个dom节点,则向td里插入节点
                if(typeof render === "object") {
                    TBODY_TD.appendChild(render);
                } else {  // 否则直接innerHTML
                    TBODY_TD.innerHTML = render;
                }
            } else {  // 没有传render时,直接插入对应值
                TBODY_TD.innerHTML = dataSource[i][columns[j].dataIndex] || ''
            }
            // 向tr中插入td
            TBODY_TR.appendChild(TBODY_TD);
            // 向tbody中插入tr
            TBODY.appendChild(TBODY_TR);
        }
    }
    return TBODY;
}
复制代码

现在,就完成了我们的基本功能,来看一下效果

上面的效果,我们传入的数据是这样的

const columns = [{
    title'姓名'
    dataIndex'name',
    align'center',
    key'name',
}, {title'年龄',
    dataIndex'age',
}, {title'工作',
    dataIndex'job',
    key'name',
}, {
    title'操作',
    key'action',
    render(text, record) => {
       return create('a''color: blue', {record})
    }
}]

function create(type, cssText, data{
    let element = document.createElement(type);
    element.style.cssText = cssText;
    element.innerHTML = '删除';
    element.onclick = click.bind(this, data.record);
    return element;
}
function click(record{
    console.log(record);
}
const dataSource = [{
    name'小红'
    age'111'
    job'前端'
}, {
    age'111'
}, {
}, {
    name'小红1'
    job1'前端'
}]
Table({columns, dataSource})
复制代码

这份数据,很显然,是会报错的

修改一下columns的数据

const columns = [{
    title: '姓名'
    dataIndex: 'name',
    align: 'center',
    key: 'name',
}, {title: '年龄',
    dataIndex: 'age',
    key: 'age',
}, {title: '工作',
    dataIndex: 'job',
    key: 'job',
}, {
    title: '操作',
    key: 'action',
    render: (text, record) => {
       return create('a''color: blue', {record})
    }
}]
复制代码

这就不报错了,至此,我们完成了目标的几个功能。

总结

文中代码已经上传到github中,地址为:table

本篇文章以antdTable组件为目标,封装了一个自己的Table组件,功能上和antd还有不小的差距,还需要我继续努力完善它。

文中代码可能会有不合理或者错的地方,还希望大家指出来,我们共同学习,共同进步~

最后,分享一下我的公众号,大家快来关注呀~

关注下面的标签,发现更多相似文章
评论