浅谈单页运用中前端分页的完成计划

简介

分页是开辟中最罕见的需求之一。
关于分页,我们议论的最多的是后端的数据库分页,这关乎到我们运用程序的机能,也是分页这个需求的中心。
而前端要做的,是把后端返回的数据呈如今页面上,事变被以为是简朴噜苏的。
在单页运用中,我们有许多中分页计划,最罕见的是无穷转动、上一页 & 下一页和页码。
本文将谈谈这三种分页体式格局。

通用

不管运用哪一种分页计划,我们都须要处置惩罚一些通用的需求,如:

  • 剖析 url,提取当前页面的参数

  • 依据返回数据天生自定义 DOM

  • 移除某个 Node 节点中的一切子元素

  • 往某个 Node 节点中插进去元素列表

// 剖析 url 中的查询字符串
// 如 http://host/items?page=5 中提取 page=5 中的 5
function parsePage() {
    var searchString = window.location.search.substr(1).split('&').filter(v => v.indexOf('page') !== -1)[0];
    var page = Number(searchString.split('=')[1]);
    return isNaN(page) ? 1 : page;
}

// 天生自定义 DOM
// generateItemView :: Object -> DOM Node
function generateItemView(object) { /* implementation */ }

// 移除 Node 中一切子节点
function removeItems(node) {
    while (node.firstChild) {
        node.removeChild(node.firstChild);
    }
}

// 往 Node 中插进去元素列表
function insertItems(node, items) {
    items.forEach(item => node.appendChild(generateItemView(item)));
}

下文的示例代码中会直接挪用这些函数,不再反复定义。

无穷转动

不管对从前端照样后端来讲,无穷转动都是我以为最简朴的分页计划。
对后端来讲,根据 pagelimit 直接查出局限,然后返回一个数组给前端即可,不须要像其他计划那样还要查询总数。
对前端来讲,直接依据后端返回的数据举行拼接即可,当后端返回一个空数组时,能够以为已到末了一页,这时刻就不须要再发要求到后端了。

// 后端返回的数据结构
// GET /items?page=5
{ items: [...] }
// 前端处置惩罚
function getItems(page) {
    fetch(`/items?page=${page}`)
        .then(res => res.json())
        .then(res => {
            if (res.items.length > 0) {
                insertItems(
                    document.getElementById('container'),
                    res.items
                );
            } else {
                alert('No more data');
            }
        });
}

无穷转动虽然完成起来简朴,用户体验也不错,但有一些致命的瑕玷:

  • 轻易涌现机能题目

  • 轻易丧失阅读进度

现在有一些计划能够处理这些瑕玷:机能题目能够经由过程动态衬着来处理,而丧失阅读进度则能够经由过程简朴的新开窗口来处理。

上一页 & 下一页

这类分页体式格局和无穷转动比起来,会庞杂一点点。
最主要是因为后端须要查询总数,然后依据当前页数来盘算是不是能够查询上一页或下一页。
固然,盘算这部份能够在后端做,也能够在前端做。

后端盘算

如果在后端盘算,那末后端要做的事变就有:

  • 查询总数

  • 盘算 hasPrevhasNext

  • 查询元素列表

而前端方面则相对简朴:

  • 依据后端返回的 hasPrevhasNext 来推断是不是须要显现上一页/下一页按钮

  • 移除容器内的一切元素,再插进去新的元素(即用新元素替代旧元素)

// 后端返回数据结构
// GET /items?page=5
{
    // hasPrev 和 hasNext 都须要后端去查询总数,然后盘算出来
    hasPrev: true,
    hasNext: true,
    items: [...]
}
// 前端处置惩罚
function getItems(page) {
    fetch(`/items?page=${page}`)
        .then(res => res.json())
        .then(res => {
            res.hasPrev
                ? document.getElementById('prevButton').style.display = 'block'
                : document.getElementById('prevButton').style.display = 'none';

            res.hasNext
                ? document.getElementById('nextButton').style.display = 'block'
                : document.getElementById('nextButton').style.display = 'none';

            var container = document.getElementById('container');
            removeItems(container);
            insertItems(container, res.items);
        });
}

这个计划完成起来比较简朴,但瑕玷是每次分页都须要查询总页数,浪费资源。

前端盘算

如果是前端盘算的话,那末后端要做的事变就相对简朴,只需再供应一个查询总数的接口即可。
而前端方面,须要做更多的事变,同时要斟酌当前端数据丧失机(如用户革新页面)的处置惩罚计划。

  • 第一次加载页面时须要挪用一次查询总数的接口,同时挪用猎取元素的接口

  • 返回数据后盘算 hasPrevhasNext,用来推断是不是须要显现上一页/下一页按钮

  • 移除容器内的一切元素,再插进去新的元素(即用新元素替代旧元素)

// 后端返回数据结构
// GET /itemsCount
{ total: 100 }

// GET /items?page=5
{ items: [...] }
// 前端处置惩罚
var total = 0;
var limit = 10;

window.onload = getItemsCount(getItems);

// 猎取总数
function getItemsCount(callback) {
    fetch('/itemsCount')
        .then(res => res.json())
        .then(res => {
            total = res.total;
            callback.call(null, parsePage());
        });
}

function getItems(page) {
    fetch(`/items?page=${page}`)
        .then(res => res.json())
        .then(res => {
            var hasPrev = page != 1;
            var hasNext = page != Math.ceil(total / limit);
            hasPrev
                ? document.getElementById('prevButton').style.display = 'block'
                : document.getElementById('prevButton').style.display = 'none';

            hasNext
                ? document.getElementById('nextButton').style.display = 'block'
                : document.getElementById('nextButton').style.display = 'none';

            var container = document.getElementById('container');
            removeItems(container);
            insertItems(container, res.items);
        });
}

这类计划能够让后端甩锅给前端,前端的活又变多拉!

页码

末了我们谈谈页码分页。
这个计划和「上一页 & 下一页」的计划很相似,差别的处所在于这个计划须要依据当前页面和总数来天生页码。
天生页码是这个计划最贫苦的处所。举个简朴的例子,假定我们的数据有 50 页,我们不可能把一切页码都显现出来,须要天生一组不一连的页码。

我们能够采纳下面的情势来显现页面:

// ------------------------------
// 我个人比较喜好用 -1 来示意省略的地区
// 在天生 DOM 的时刻,能够用省略号来展现
// ------------------------------
// 假定当前是第 1 页
[1, 2, 3, -1, 50]

// 假定当前是第 3 页
[1, 2, 3, 4, 5, -1, 50]

// 假定当前是第 25 页
[1, -1, 23, 24, 25, 26, 27, -1, 50]

// 假定当前是第 48 页
[1, -1, 46, 47, 48, 49, 50]

// 假定当前是第 50 页
[1, -1, 48, 49, 50]

天生页码的准绳一般都是:

  • 第一页和末了一页必需展现

  • 其他页面按需展现,一般是当前页面的前后两页(即 x +- 2)

  • 当页数少于 10 页的时刻,直接显现出一切页码(为何是 10 页?其实在满足前两个准绳的情况下,只需 7 页省略号就会一般显现了。但页数较少的情况下显现省略号觉得怪怪的。)

var lastPage = Math.ceil(total / limit);

// 依据当前页天生动态页码
function genPages() {

    if (lastPage <= 10) {
        return Array(lastPage).fill().map((v, i) => i + 1);
    }

    // dynamicPages 为除第一页和末了一页以外的页码,-1 示意省略号
    var dynamicPages;

    if (page === 1) {
        dynamicPages = [2, 3, -1];

    } else if (page === 2) {
        dynamicPages = [2, 3, 4, -1];

    } else if (page === 3) {
        dynamicPages = [2, 3, 4, 5, -1];

    } else if (page === lastPage - 2) {
        dynamicPages = [-1, page - 2, page - 1, page, page + 1];

    } else if (page === lastPage - 1) {
        dynamicPages = [-1, page - 2, page - 1, page];

    } else if (page === lastPage) {
        dynamicPages = [-1, page - 2, page - 1];

    } else {
        dynamicPages = [-1, page - 2, page - 1, page, page + 1, page + 2, -1];
    }

    dynamicPages.unshift(1);
    dynamicPages.push(lastPage);

    return dynamicPages;
}

天生动态页码这部份的逻辑,不管放在前端照样后端都影响不大,能够根据本身须要去挑选。
至于其他部份的细节,和「上一页 & 下一页」相似,这里就不再反复了。

出处

http://scarletsky.github.io/2…

参考资料

https://github.com/xitu/gold-…

    原文作者:scarlex
    原文地址: https://segmentfault.com/a/1190000006881082
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞