从Toast到Pagination: 如何优雅地写组件

1,760 阅读3分钟

作者:孙辉,美团金融前端团队成员。15年毕业加入美团,相信技术,更相信技术只是大千世界里知识的一种,个人博客: sunyuhui.com

最近在做的新业务(PC端)里面,自己写了几个组件,既有简单的toast,也有稍微复杂一点的pagination(分页),在这里记录下自己的思考过程,希望对大家有帮助 ~

Toast

实现基本功能

toast大家已经很熟悉了,其应用场景是:完成某个操作后,给用户一个提示,一定时间后消失

基于这个思路,我们很快能实现出一个基于jQuery能用初版toast

其中关键的代码只有几行:

var elementContent = '<div class="toast-container" style="' + style + '"> <span class="toast-content"> ' + options.content + ' </span> </div>';
$('body').append(elementContent);
//一定时间后消失
var timeout = setTimeout(function() {
    $('.toast-container').remove();
}, options.time*1000);
使用优化

上面的toast已经可以用了,但是我在用的时候,发现有一点不方便,因为我每次调用的时候都需要传入一个object对象,即便我就想用默认的样式,默认的消失时间。那我能不能就传入一个我想要给用户展示的字符串呢?

简单改造一下:

...
//参数为字符串时,将其作为content属性值
if(Object.prototype.toString.call(options) === '[object String]') {
    options = $.extend(defaultParam, {
        content: options
    });
} else if(Object.prototype.toString.call(options) === '[object Object]') {
        options = $.extend(defaultParam, options);
} else {
        console.error('only support String or Object for param');
        return;
}
...

没有花费太多心思,我们就能让使用方式变得更加简单,有了支持多参数形式的toast

去掉第三方依赖

我们在实现toast时使用了jQuery这个第三方库,不得不说,我有时候真心觉得jQuery让前端门槛降低了不少,但是,业务技术架构并不是一成不变的,有的项目里并不会使用jQuery。那么我们就需要提供一个不依赖第三方库toast

只需要将上面用到jQuery的地方使用原生JS实现就行了,其中有个深拷贝函数$.extend大家需要关注,在这里我们就不细说了。

完整代码:原生JavaScript实现的toast

到这里我们就实现了一个功能比较完整的toast,整个代码很简单,但我重点想说的是不断优化的过程:

  • 第一个版本,我们的要求是:能用
  • 第二个版本,我们的要求是:用起来方便
  • 第三个版本,我们的要求是:不依赖第三方库,能适应不同技术架构

Pagination

我们通过写toast组件的过程,已经了解了如何写好一个组件,现在我们将这个思路应用到一个稍微有点复杂的组件上:分页组件。

分页组件通常是和表格一起用的,使用场景是:将比较大的数据量分页,每页展示固定条数的数据,用户可以通过分页组件自由选择查看任何页码的数据,我们梳理下思路,分为这么几步:

  1. 通过当前页码总页数展示出页码选择器
  2. 给每一页绑定点击事件,点击时获取数据,并且更新页码选择器

其中有个词是:页码选择器,其实指的就是这个东西:

页码选项
页码选项

如何展示出页码选择器?,我发现有篇博客讲解的思路非常好:如何写一个简单的分页,但是在代码细节上,对于不熟悉分页的人不太好理解,我沿用这个思路,具体细节上有修改。

总体思路就是以当前页码为分割点,分别得到之前和之后的页码。

现在我们用代码初版pagination组件实现这个效果(忽略CSS),得到上图中的页码选择器,其中关键代码如下,我做了详细注释:

/**
 * [getPage 获取页码展示]
 * @param  {[type]} currentPage [当前页码]
 * @param  {[type]} totalPage  [页码总数]
 * @return {[type]}             [description]
 */
function getPage(currentPage, totalPage) {
    //显示:第一页,当前页,当前页的前后两页,最后一页
    //以当前页为分割点,分别得到当前页前面的页码和后面的页码
    var pageStr = '<a class="active">' + currentPage + '</a>';
    // 将当前页前后2页的页码展示出来
    for(var i = 1; i<=2; i++) {
        //得到当前页前两页的的页码
        //其中的1指第一页
        if(currentPage > i+1) {
            pageStr = '<a>' + (currentPage - i) + '</a>' + pageStr;
        }
        //得到当前页后两页的页码
        if(currentPage+i < totalPage) {
            pageStr = pageStr + '<a>' + (currentPage + i) + '</a>';
        }
    }
    //得到当前页前面用...表示的页码
    //两个1分别表示第一页,和当前页
    if( currentPage > 2+1+1 ) {
        pageStr = ' ... ' + pageStr;
    }

    //得到上一页
    if(currentPage > 1) {
        pageStr = '<a class="prePage">上一页</a><a>1</a>' + pageStr;
    }

    //得到当前页后面用...表示的页码
    //其中1表示最后一页,当前页已经在前面计算过,这里不再计算
    if( currentPage+2+1 < totalPage ) {
        pageStr = pageStr + ' ... ';
    }

    //得到下一页
    if( currentPage < totalPage ) {
        pageStr = pageStr + '<a>' + totalPage + '</a><a class="lastPage">下一页</a>';
    }

    return pageStr;
}

最终我们达到的效果是这样的:

分页效果图
分页效果图

其中.pagination-container这个元素是整个分页组件的容器,直接在html页面中定义就行。

在上面的效果图中,我们点击不同的页码只是【跳转到了不同的页面】,但在实际项目中,我们通常都是获取要获取数据,更新表格。

我们把bindEvent函数稍微更新一下

var $this = $(this);
var pageNum;
var currentPage = +$('.pagination-list a.active').text();
if( $this.hasClass('prePage') ) {
    //点击【上一页】
    pageNum = currentPage - 1;
} else if( $this.hasClass('lastPage') ) {
    //点击【下一页】
    pageNum = currentPage + 1;
} else {
        pageNum = +$this.text();
}
//获取表格数据
//执行ajax之前的回调函数,比如加个loading
options.beforeCallback && options.beforeCallback();
$.ajax({
    url:options.url,
    type: options.type,
    data: options.data
}).done(function(res){
    //执行ajax之后的回调函数,比如隐藏loading
    options.afterCallback && options.afterCallback();
    //请求成功后的回调函数
    options.callback && options.callback(res);
    // 更新页码
    var pageHtml = getPage(pageNum, options.totalPage);
    $('.pagination-list').html(pageHtml);
}).fail(function(error){
    //执行ajax之后的回调函数,比如隐藏loading
    options.afterCallback && options.afterCallback();
    alert('请求出错,请重试');
});

这样我们就完成了一个比较完整的分页组件

去掉第三方依赖

在这个组件里去掉依赖就是去掉对jQuery的依赖,我们只需要把使用jquery方法的地方用原生JavaScript实现一遍就行。总结一下,我们需要自己实现深拷贝函数事件代理函数发送Ajax。完整代码:原生JavaScript实现的分页组件

大家可以对比一下,去掉了jQuery之后,我们多写了多少代码啊。其中有很多细节需要考量,所以我一直觉得写原生JavaScript才能真正考察一个前端的功底。

总结

ok, 到这里就差不多了,本文重点不在于告诉大家如何去实现某一个具体的组件(毕竟我们只涉及到两个组件),而在于阐述一种观点:我们写的组件即便只是用在我们自己的业务中,也不要满足于能用就行,一步步优化组件,优化功能,能适应不同技术场景,不仅能提升技术能力,还能让我们意识到并且实践如何把一件事从60分做到90分

最后,团队为了招聘方便,整了个公众号,主要是一些招聘信息,团队信息,所有的技术文章在公众号里也可以看到,对了,如果你想去美团其他团队,我们也可以帮你内推哦 ~

二维码
二维码

参考:

如何写一个简单的分页