本文是使用 Typescript 开发类 antd 分页器,并发布 npm 的第二篇,因为最近在业务中使用了 antd 中的 Pagination 开发了相关业务,而自己又对组件的封装由很有兴趣,所以打算用 ts 来封装一个具有 antd 所有功能的 Pagination。相信你也能轻轻松松的发布一个 npm 组件了。

相关系列文章

从零开始实现类 antd 分页器(一):搭建项目架构

从零开始实现类 antd 分页器(二):分页核心代码

从零开始实现类 antd 分页器(三):发布npm

写作过程中有些方法的具体实现没有讲到,大家可以自行查看源码。本案例项目仓库:

闲来无事,造个轮子 —— darrell-wheels

为了写作方便,antd 的 分页器我统一以 antd-pagination 代替,自己的分页器统一以 my-pagination 代替。

效果图

基础 + 更多

跳转页数 [ showQuickJumper ] + 改变页数 [ showSizeChanger ]

迷你 [ size: 'small' ]

简洁 [ simple: true ]

显示总数 [ showTotal ]

修改上一步和下一步相应文字 [ itemRender ]

分页逻辑

这里面的逻辑是实现这个分页器最最关键的地方,同时也是分页器的难点所在。

我们要计算出每一页的页码排布情况,什么时候该显示省略号,什么时候改成全部显示等等。

antd 的分页

我试了一下 antd-pagination 的页码显示,假设有 30 页,它的页码是如下分布的,我们以 allPages 代表总页数,current 代表当前是第几页:

// allPages = 30

当 current = 1, 显示 1 2 3 4 5 ... 30

当 current = 2, 显示 1 2 3 4 5 ... 30

当 current = 3, 显示 1 2 3 4 5 ... 30

当 current = 4, 显示 1 2 3 4 5 6 ... 30

当 current = 5, 显示 1 ... 3 4 5 6 7 ... 30

...

当 current = 13, 显示 1 ... 11 12 13 14 15 ... 30

当 current = 14, 显示 1 ... 12 13 14 15 16 ... 30

当 current = 15, 显示 1 ... 13 14 15 16 17 ... 30

当 current = 16, 显示 1 ... 14 15 16 17 18 ... 30

...

当 current = 26, 显示 1 ... 24 25 26 27 28 ... 30

当 current = 27, 显示 1 ... 25 26 27 28 29 30

当 current = 28, 显示 1 ... 26 27 28 29 30

当 current = 29, 显示 1 ... 26 27 28 29 30

当 current = 30, 显示 1 ... 26 27 28 29 30

复制代码

在 antd-pagination 有一个参数 showLessItems,当它为 true 时,意思是当前页面 左右显示各 1 个,为 false 时,代表当前页面 左右各显示 2 个。在这里我们把 当前页面 左右显示几个 暂且记为 pageBufferSize,

也就是说,antd-pagination 的 pageBufferSize 只有两个值 1 和 2,不过两个已经完全够了。

上面的例子,pageBufferSize = 2,及 showLessItems 为 false。

找规律

接着观察上面这一组数据,我们可以找一下规律,粗粗一看,我们会发现:

第 1 页 到 第 3 页 显示的页码是一样的,只有 一个后面省略号

第 4 页 比 前 3 页 多了一个页码,但是还是只有 一个后面省略号(临界点)

第 5 页 开始,到 第 26 页 出现了 两个省略号,并且中间都是 5 个

第 27 页 跟第四页类似 又回到了 一个前面省略号(临界点)

第 28 页 到 第 30 页,显示的页面一样,并只有 一个前面省略号。

代码实现

我们在这里先暂时不考虑复杂的 dom 输出,只使用简单的字符串输出。

这里笔者想到两种思路:

第一种思路

简单粗暴:就是把用代码翻译上面我们发现的规律,我们可以用 allPages、current、pageBufferSize 来表示:

// 临界点

当 current = 5,转化为代码 1 + pageBufferSize * 2

当 current = 26,转化为代码 allPages - pageBufferSize * 2

// 在临界点 区域内,都是双省略号

current >= 1 + pageBufferSize * 2 && current <= allPages + pageBufferSize * 2

// 然后在对 第4页 和 第27页做一下特殊处理

// 这两页的页码虽然只有一个省略号,但是比正常的会多出一个页码

当 current = 4,转化为代码 pageBufferSize * 2

当 current = 27,转化为代码 allPages - pageBufferSize * 2 + 1

// 接下来就是最简单的

当 current = 1 || 2 || 3,转化为代码 < pageBufferSize * 2 - 1

当 current = 28 || 29 || 30,> allPages - pageBufferSize * 2 + 1

复制代码

第二种思路

我们主要来讲一下第二种思路,这个也是 antd-pagination 内使用的方法,我们接下来的方法也只满足 pageBufferSize 为 1 或者 2 。

我们定义一个函数 showPages 方法来实现相关的逻辑;

/**

* 输出每一页 显示的页码 的 字符串

* @param current:当前页码;

* @param allPages:总页数;

* @param pageBufferSize:页数左右两边显示几个(1个 或 2个)

*/

function showPages (current, allPages, pageBufferSize){

let str = '';

...

// 待完成的逻辑

...

return str.trim();

}

// 总页数

let total = 30;

// 循环输出每页的页码字符串

for (let i = 1; i <= total; i++) {

console.log(showPages(i, total, 2));

}

复制代码

首先 antd-pagination 当总页数小于等于 5 + 2 * pageBufferSize 的时候,不管 current 是 第几页,所有的页码都会展现,我们可以在 showPages 添加如下代码:

// 当总页数 <= 5 + 2 * pageBufferSize

function showPages (current, allPages, pageBufferSize){

if (allPages <= 5 + pageBufferSize * 2) {

for (let i = 1; i <= allPages; i++) {

str = str + ' ' + i;

}

}

}

复制代码

此时我们设置 allPages = 8,在浏览器中我们可以看到如下图所示:

观察上面的字符串我们发现,当有两个省略号的时候,前面字符肯定是 1 ...,最后两个字符肯定是 ... 30。

1. 先循环输出 当前页码 与 页码前后 pageBufferSize 个数

我们在代码里面定义一个 left,代表字符串循环从这个数开始;再定义 right,代表字符串循环到此结束。left 的值根据前几页的 current 和 pageBufferSize ,我们很简单的可以得到。

let left = Math.max(1, current - pageBufferSize);

复制代码

同理可以得到 right 的值:

let right = Math.min(current + pageBufferSize, allPages);

复制代码

因为 前三页 和 后三页 都是显示 5 个数的,所以在这种情况下,我们要对 left 和 right 在根据 pageBufferSize 做一个调整:

if (current - 1 <= pageBufferSize) {

right = 1 + pageBufferSize * 2;

}

if (allPages - current <= pageBufferSize) {

left = allPages - pageBufferSize * 2;

}

复制代码

接着我们便可以循环输出了:

for (let i = left; i <= right; i++) {

str = str + ' ' + i;

}

复制代码

2. 然后再去拼相关的省略号

接下来我们就要给上面的 str 拼省略号了。

这这我们在第一种思路里面讲过,我们可以很快的写出如下代码:

if (current - 1 >= pageBufferSize * 2) {

str = '... ' + str;

}

if (allPages - current >= pageBufferSize * 2) {

str = str + ' ...';

}

复制代码

这里面需要注意一个点,这个判断方法在 pageBufferSize = 1 的时候,在第3页 和 allPages - 2 页会出现问题,如图所示:

所以我们要在加上对这个的判断:

if (current - 1 >= pageBufferSize * 2 && current !== 1 + 2) {

str = '... ' + str;

}

if (allPages - current >= pageBufferSize * 2 && current !== allPages - 2) {

str = str + ' ...';

}

复制代码

3. 最后再去比较第一个值和最后一个值 是不是 1 和 总页数。

省略号我们加完之后,我们就只剩最后一步了。我们需要判断 第一步的 left 和 right 是不是第 1 页和 最后一页,我们可以写出以下代码:

if (left !== 1) {

str = 1 + ' ' + str;

}

if (right !== allPages) {

str = str + ' ' + allPages;

}

复制代码

到这里我们的分页逻辑算是大功告成,完整的代码如下:

function showPages (current, allPages, pageBufferSize){

let str = '';

if (allPages <= 5 + pageBufferSize * 2) {

for (let i = 1; i <= allPages; i++) {

str = str + ' ' + i;

}

} else {

let left = Math.max(1, current - pageBufferSize);

let right = Math.min(current + pageBufferSize, allPages);

if (current - 1 <= pageBufferSize) {

right = 1 + pageBufferSize * 2;

}

if (allPages - current <= pageBufferSize) {

left = allPages - pageBufferSize * 2;

}

for (let i = left; i <= right; i++) {

str = str + ' ' + i;

}

if (current - 1 >= pageBufferSize * 2 && current !== 1 + 2) {

str = '... ' + str;

}

if (allPages - current >= pageBufferSize * 2 && current !== allPages - 2) {

str = str + ' ...';

}

if (left !== 1) {

str = 1 + ' ' + str;

}

if (right !== allPages) {

str = str + ' ' + allPages;

}

}

return str.trim();

}

复制代码

打开浏览器,分别测试一下 pageBufferSize 为 1 和 2 时,如下图:

总算是搞定了,终于可以快乐的进行下一步的开发了。

大家如果还有什么不懂,还可以看一下这篇文章 十五分钟--分页逻辑--包学包会,写的很详细。

基本架构

插件的基本架构

首先我们在 src/components/pagination/src 下创建 pagination,并新建 index.ts 文件,

import '../../../../styles/normal.less';

import '../../../../styles/pagination/index.less';

//  默认配置

import defaults from "./defaults";

import { PaginationConfig } from '../../../../types/index';

class Pagination {

private options: PaginationConfig;

private pageElement: any;

private total: any;

private current: any;

private pageCount: any;

private pageSize: any;

constructor(selector: any, options = {}) {

// 默认配置

this.options = Object.assign(defaults, options);

this.init(selector);

}

/**

* 初始化相应的 分页函数

* @param selector

*/

private init (selector:any) {

// 分页器元素

this.pageElement = ?(selector)[0];

// 数据总数

this.total = this.options.total;

// 当前页码

this.current = this.options.current || this.options.defaultCurrent;

// 一页显示多少数据

this.pageSize = this.options.pageSize || this.options.defaultPageSize;

// 总页数

this.pageCount = Math.ceil(this.total / this.pageSize);

// 渲染相应的 Dom

this.renderPages();

// 改变页数并触发事件

this.changePage();

}

}

复制代码

同时我们在 src/types 文件夹下新建 index.ts,此文件定义了 my-pagination 参数的接口:

// 一些配置参数

// 解释在 option.ts 上

export interface PaginationConfig {

// 当前页数

current?: number;

// 默认第几页

defaultCurrent?: number;

// 默认的 pageSize

defaultPageSize?: number;

// 是否 disables

disabled?: boolean;

// 在只有一页的时候,是否隐藏

hideOnSinglePage?: boolean;

// 用于自定义页码的结构,可用于优化 SEO

itemRender?: (

page: number,

type: 'page' | 'prev' | 'next' | 'jump-prev' | 'jump-next',

originalElement: any

) => any;

// 每页条数

pageSize?: number;

// 指定每页可以显示多少条

pageSizeOptions?: string[];

// 这个是页数周围各显示几页;true:2;false:1;

showLessItems?: boolean;

// 是否可以快速跳转至某页

showQuickJumper?: boolean;

// 是否可以改变 pageSize

showSizeChanger?: boolean;

// 用于显示数据总量和当前数据顺序

showTotal?: (total: number, ranger: any) => any;

simple?: boolean; // 当添加该属性时,显示为简单分页

// 当为「small」时,是小尺寸分页

size?: string;

// 数据总数

total: number;

// 页码改变的回调,参数是改变后的页码及每页条数

onChange?: (page: number, pageSize: number) => void;

//pageSize 变化的回调

onShowSizeChange?: (current: number, size: number) => void;

}

复制代码

首先我们在 src/components/pagination/src 新建 defaults.ts 文件,定义分页器的默认参数:

import { PaginationConfig } from '../../../../types/index'

import { noop } from '../../../../helpers/utils'

const defaults: PaginationConfig = {

defaultCurrent: 1,

defaultPageSize: 10,

hideOnSinglePage: false,

pageSizeOptions: ['10', '20', '30', '40'],

showLessItems: false,

showQuickJumper: false,

showSizeChanger: false,

size: '',

total: 0,

onChange: noop,

onShowSizeChange: noop,

}

export default defaults;

复制代码

插件的工具函数

我们在 src/helpers 新建 utils.ts,包含了一些在代码中使用到的工具函数:

noop:空函数

addClass(elem: any, className: any):添加 class 名字

removeClass(elem: any, className: any):移除 class 名字

hasClass(elem: any, className: any):是否拥有相应的 class

addEvent(element: any, type: any, handler: any):添加事件的方法函数

removeHandler(element: any, type: any, handler: any):移除事件的方法函数

?(selector:any, context?:any):模仿 jQuery $()

渲染 Dom

Dom 结构

为了更加清晰的讲一下 dom 结构,我们可以看一下完成后是怎么样的。如下图:

我们可以看到分页器的 Dom 结构是 以 ul 为包裹元素,每一个子元素都是一个 li,在这里有两种情况。

li 是 页数,在 li 之中是 a 标签,标签里页数。

li 是 操作页数的按钮(上一页/下一页),在 li 之中是 a 标签,标签上是相应的符号。

渲染

首先完善上面的 renderPages 函数。

/**

* 渲染分页器

*/

private renderPages () {

if (!this.pageElement) return console.error('请设置包裹容器');

this.pageElement.innerHTML = ""; // 清空 html

const current = this.current;

const pageSize = this.pageSize;

const total = this.total;

const pageCount = this.pageCount;

let fragment: any = ''; // 创建代码片段

fragment = this.showPages();

let UlEle = document.createElement("ul"); // 创建包裹元素

UlEle.appendChild(fragment);

UlEle.setAttribute('class', 'darrell-pagination');

this.pageElement.appendChild(UlEle); // 塞入到 分页器中

}

复制代码完善 showPages 函数,此函数将 分页逻辑 里面得出的字符串输出的转化为 Dom 输出:

/**

* 通过页数,渲染相应的 分页 html

*/

private showPages () {

// 当前页数

const current = this.current;

// 总页数

const allPages = this.pageCount;

// 页数周围显示几个

const pageBufferSize = this.options.showLessItems ? 1 : 2;

// 代码片段

let fragment = document.createDocumentFragment();

if (allPages <= 5 + pageBufferSize * 2) {

// 通过 renderDom 渲染 html

const fragmentHtml = this.renderDom(1, allPages);

fragment.appendChild(fragmentHtml);

} else {

let left = Math.max(1, current - pageBufferSize);

let right = Math.min(current + pageBufferSize, allPages);

if (current - 1 <= pageBufferSize) {

right = 1 + pageBufferSize * 2;

}

if (allPages - current <= pageBufferSize) {

left = allPages - pageBufferSize * 2;

}

// 通过 renderDom 渲染 html

const fragmentHtml = this.renderDom(left, right);

fragment.appendChild(fragmentHtml);

if (current - 1 >= pageBufferSize * 2 && current !== 1 + 2) {

// 添加前面的省略号

this.addFragmentBefore(fragment, [Pagination.PAGE_INFOS[2]]);

}

if (allPages - current >= pageBufferSize * 2 && current !== allPages - 2) {

// 添加后面的省略号

this.addFragmentAfter(fragment, [Pagination.PAGE_INFOS[3]]);

}

if (left !== 1) {

// 如果不是第一页,插入第一页

this.addFragmentBefore(fragment, [

this.getPageInfos('darrell-pagination-item', 1)

]);

}

if (right !== allPages) {

// 如果不是最后一页,插入最后

this.addFragmentAfter(fragment, [

this.getPageInfos('darrell-pagination-item', allPages)

]);

}

}

// 添加 前一页的 dom

this.addFragmentBefore(fragment, [Pagination.PAGE_INFOS[0]]);

// 添加 后一页的 dom

this.addFragmentAfter(fragment, [Pagination.PAGE_INFOS[1]]);

return fragment;

}

复制代码完善 renderDom 函数,这个主要用来渲染页码;同时完善 addFragmentBefore 与 addFragmentAfter 函数,向代码片段前后插入代码。

/**

* 循环渲染相应的 Li 元素

* @param begin

* @param end

*/

private renderDom (begin: number, end: number) {

let fragment = document.createDocumentFragment();

let str = "";

for (let i = begin; i <= end; i++) {

this.addFragmentAfter(fragment, [

this.getPageInfos('darrell-pagination-item', i);

]);

}

return fragment;

}

/**

* 往 html 片段中 前 添加 html

* @param fragment

* @param datas

*/

private addFragmentBefore (fragment: any, datas: any) {

fragment.insertBefore(this.createLiHtml(datas), fragment.firstChild);

}

/**

* 往 html 片段中 后 添加 html

* @param fragment

* @param datas

*/

private addFragmentAfter (fragment: any, datas: any) {

fragment.appendChild(this.createLiHtml(datas));

}

复制代码完善 createLiHtml 函数,这个是最终我们生成 Dom 节点的函数。 在之前的所有函数我们其实都没有真正意义上生成 li 的 Dom 节点。

/**

* 创建 Li 元素

* @param liItemInfo

*/

private createLiHtml (liItemInfo: Array) {

let fragment = document.createDocumentFragment();

// 创建 li 标签

let liEle = document.createElement("li");

// liItemInfo 会传入相应的 id,className,和 a 标签中的内容

const id = liItemInfo[0].id;

const className = liItemInfo[0].className;

const content = liItemInfo[0].content;

// 当前页码

const current = this.current;

// 添加样式、id、与 页码

liEle.setAttribute('class', `${className} liEventTarget`);

liEle.setAttribute('id', id);

liEle.setAttribute('data-page', content);

let aEle;

if (id === 'prev') {

// 渲染上一页的 icon,getEveryIconType() 函数是返回一个 icon 的 dom

aEle = this.getIcon(this.getEveryIconType().prevIconEle)

} else if (id === 'next') {

// 渲染下一页的 icon

aEle = this.getIcon(this.getEveryIconType().nextIconEle)

} else if (id === 'jump-prev') {

// 渲染上一批的 icon

aEle = this.getIcon(this.getEveryIconType().jumpPrevWrapEle)

} else if (id === 'jump-next') {

// 渲染下一批的 icon

aEle = this.getIcon(this.getEveryIconType().jumpNextWrapEle)

} else if (id === 'page') {

// 渲染页数

if (current === parseInt(content, 10)) {

// 高亮选中的页数

addClass(liEle, Pagination.CLASS_NAME.ITEM_ACTIVE);

}

let aEleNew = document.createElement("a");

aEleNew.innerHTML = content;

aEle = aEleNew

}

// 添加到代码片段中

liEle.appendChild(aEle);

fragment.appendChild(liEle);

return fragment;

}

复制代码

一些说明

上面我们列出的函数里有些函数没有讲到,具体是如下的作用。

Pagination.PAGE_INFOS:返回 前一步/后一步/前几步/后几步 的 id 与 样式

getPageInfos :返回页数的数据

CLASS_NAME:样式名的定义

this.getEveryIconType():此函数返回图标的相关 dom ,如图:

事件绑定

事件绑定我们可以通过 事件委托 来做。

事件委托就是利用冒泡的原理,把事件加到父元素或祖先元素上,触发执行效果,大家可以参考

我们将事件绑定在 this.pageElement 上,同时判断元素 li 上的 id 属性来进行分发事件:

import { addEvent } from '../../../../helpers/utils';

/**

* 改变页数

*/

private changePage () {

if (!this.pageElement) return;

// 获取根元素

let pageElement = this.pageElement;

// 给根元素绑定事件

addEvent(pageElement, 'click', (ev: any) => {

let e = ev || window.event;

let target = e.target || e.srcElement;

// 通过 a 元素,找到其父元素 li 元素。

const parentNode = target.parentNode;

if (parentNode && parentNode.nodeName.toLocaleLowerCase() == "li") {

if (parentNode.id === "prev") {

// 前一页

if (!this.hasPrev()) return false;

this.prevPage();

} else if (parentNode.id === "next") {

if (!this.hasNext()) return;

// 后一页

this.nextPage();

} else if (parentNode.id === "jump-prev") {

// 前一批

this.jumpPrevPage();

} else if (parentNode.id === "jump-next") {

// 后一批

this.jumpNextPage();

} else if (parentNode.id === "page") {

// 页面跳转

const page = parentNode.dataset.page;

this.goPage(parseInt(page, 10));

} else {

// 直接 return,不做任何事情

return;

}

// 重新渲染分页器

this.renderPages();

}

});

}

复制代码里面涉及到的页面跳转函数我这里就不列举了,具体可以看源码。

回调事件

回调事件有两个:

onChange:页数改变

onShowSizeChange:pageSize 变化的回调

我们先来实现 onChange,onShowSizeChange 我们在后面会讲到。

其实很简单,只要在 changePage 函数里面,重新渲染函数 renderPages 之后,执行一些回调函数就行:

private changePage () {

...

// 重新渲染分页器

this.renderPages();

// 如果有参数有 onChange,就将 页数,和 pagesize 传入执行

this.options.onChange && this.options.onChange(this.current, this.pageSize);

...

}

复制代码

my-pagination 更多参数

更多参数其实我们都是围绕 renderPages 这个函数进行的。

disabled

禁用分页器,其实就是在包裹元素 ul 加上 darrell-pagination-disabled 样式。同时在事件绑定的地方,需要加上分页器是 disabled,就不绑定相应的事件

/**

* 渲染分页器

*/

private renderPages () {

...

const isDisabled = this.options.disabled;

...

let UlEle = document.createElement("ul");

UlEle.appendChild(fragment);

UlEle.setAttribute('class', 'darrell-pagination');

if (isDisabled) {

addClass(UlEle, 'darrell-pagination-disabled');

}

...

}

/**

* 改变页数

*/

private changePage () {

...

const isDisabled = this.options.disabled;

// 不是 disabled 时,做事件绑定

if (!isDisabled) {

// 做事件绑定

}

}

复制代码

hideOnSinglePage

只有一页时隐藏,只要在 renderPages 中,渲染 dom 之前判断 总页数是否为一

/**

* 渲染分页器

*/

private renderPages () {

...

const hideOnSinglePage = this.options.hideOnSinglePage;

// 如果是 一页,就直接不去渲染 dom 了。

if (hideOnSinglePage) {

if (pageCount === 1) {

return false;

}

}

...

}

复制代码

size

当 size=small 时,在包裹元素 ul 加上 mini 就行

/**

* 渲染分页器

*/

private renderPages () {

...

const isSmall = this.options.size;

...

let UlEle = document.createElement("ul");

UlEle.appendChild(fragment);

UlEle.setAttribute('class', 'darrell-pagination');

if (isSmall) {

addClass(UlEle, 'mini');

}

...

}

复制代码

simple

当其为 true 时,我们只需要简单的展示当前页数和总页数就行了

/**

* 渲染分页器

*/

private renderPages () {

...

const simple = this.options.simple;

...

let fragment: any = '';

if (!simple) {

fragment = this.showPages();

} else {

// 渲染简单分页的 dom

fragment = this.simplePages();

}

...

}

复制代码this.simplePages() 这个参数时用来生成简单分页的 dom 的,具体可以看源码

total

数据总数,用于显示数据总量和当前数据顺序,是一个函数,输出一个以 页数 与 总页数 相关的字符串。

/**

* 渲染分页器

*/

private renderPages () {

...

const showTotal = this.options.showTotal;

...

// 有 showTotal 并且不是 simple

if (showTotal && typeof showTotal === 'function' && !simple) {

let LiTextEle = document.createElement("li");

// 加上相关的样式

LiTextEle.setAttribute('class', 'darrell-pagination-total-text');

// 将总页数,和这页显示从第几条开始到第几条结束 的 range 传入 showTotal

const TotalText = showTotal(

this.total,

[

this.total === 0 ? 0 : (current - 1) * pageSize + 1,

current * pageSize > total ? total : current * pageSize,

]

);

LiTextEle.innerHTML = TotalText;

UlEle.insertBefore(LiTextEle, UlEle.firstChild);

}

...

}

复制代码具体字符串有使用者定义 ,

showQuickJumper

是否可以快速跳转至某页,我们要生成一个快速跳转的 dom,插入到包裹容器之中 。

/**

* 渲染分页器

*/

private renderPages () {

...

const showQuickJumper = this.options.showQuickJumper;

...

if (showQuickJumper && !simple) {

// 生成 快速跳转至某页的 dom

let quickJumperEle = this.createQuickJumperDom();

UlEle.appendChild(quickJumperEle);

}

...

}

复制代码this.createQuickJumperDom():这个函数是快速生成到某页的 Dom,具体可以看源码

showSizeChanger

是否可以改变 pageSize,我们要生成一个页面改变的类 select 元素,插入到包裹容器之中。

关于类 select 对象,我的做法是新建一个 select 类,做法和分页器一样,有时间我会在写一个仿 Antd 的 select 框,使用就是 new 一个 select 对象,并传入 包裹 id 和 相关的参数。

/**

* 渲染分页器

*/

private renderPages () {

...

const showSizeChanger = this.options.showSizeChanger;

...

if (showSizeChanger && !simple) {

// 放改变 size 的框

let LiEle = document.createElement("li");

LiEle.setAttribute('id', 'select');

LiEle.setAttribute('class', 'darrell-pagination-options');

UlEle.appendChild(LiEle);

}

...

this.pageElement.appendChild(UlEle);

if (showSizeChanger && !simple) {

new Select('#select', {

// 当前的 pageSize

value: this.pageSize,

// 当前页数

currentPage: this.current,

// 传入是否 disabled

disabled: isDisabled,

// 执行 pageSize 改变后的回调

onShowSizeChange: (current: number, size: number) => {

// pageSize

this.pageSize = size;

// 总页数

this.pageCount = Math.ceil(this.total / this.pageSize);

// 当前页数

if (current > this.pageCount) {

current = this.pageCount;

} else if (current < 1) {

current = 1

}

this.current = current;

// 重新渲染 分页器

this.renderPages();

// 执行 pageSize 改变后的回调

this.options.onShowSizeChange && this.options.onShowSizeChange(current, size);

},

});

}

...

}

复制代码select 类,我写在了 src/components/pagination/src/select 这个文件夹下,用于生生相应的 select 元素,具体可以看源码

itemRender

就是我们可以自定义分页器的文案,增强 seo,其本身也是一个函数,接受三个参数 current:当前页数;type:按钮类型;originalElement:元素的 dom;从而使我们能自定义文字内容。

这个函数运行的时机是在渲染元素时候去做,即在函数 createLiHtml 中

/**

* 默认的 render 方法

*/

static defaultItemRender = (page: number, type: 'page' | 'prev' | 'next' | 'jump-prev' | 'jump-next', element: any) => {

return element;

}

/**

* 创建 Li 元素

* @param liItemInfo

*/

private createLiHtml (liItemInfo: Array) {

// itemRender 函数

const itemRender = this.options.itemRender || Pagination.defaultItemRender;

if (id === 'prev') {

...

// 前一页

aEle = itemRender(

this.getPrevPage(),

'prev',

this.getIcon(this.getEveryIconType().prevIconEle)

)

} else if (id === 'next') {

...

// 后一页

aEle = itemRender(

this.getNextPage(),

'next',

this.getIcon(this.getEveryIconType().nextIconEle)

)

} else if (id === 'jump-prev') {

...

// 前一批

aEle = itemRender(

this.getJumpPrevPage(),

'jump-prev',

this.getIcon(this.getEveryIconType().jumpPrevWrapEle)

)

} else if (id === 'jump-next') {

...

// 后一批

aEle = itemRender(

this.getJumpNextPage(),

'jump-next',

this.getIcon(this.getEveryIconType().jumpNextWrapEle)

)

} else if (id === 'page') {

...

// 渲染页数

aEle = itemRender(

parseInt(content, 10),

'page',

aEleNew,

)

}

...

}

复制代码this.getIcon:这个方法是返回 icon 的 dom 元素

this.getEveryIconType:上面我们有讲到过,具体可以看源码

my-pagination 样式

对于样式,笔者在这里就不细说了,主要就是沿用了 antd-pagination 的样式,同时还增加了 normal.css,用于对页面的初始化。具体的大家可以参考样式源码。

小结

这一节我们详细的讲了一下分页核心逻辑的开发,从思路分析到具体的代码实现,同时笔者也参考了 手把手教你用原生JavaScript造轮子 和 十五分钟--分页逻辑--包学包会 这两篇文章,那么接下去我们就可以将组件发布到 npm 上去了,大家可以移步下一节 从零开始实现类 antd 分页器(三):发布npm 查看具体的 npm 发布流程。

参考内容

antd自定义分页器_从零开始实现类 antd 分页器(二):分页核心代码相关推荐

  1. start uml怎么自动生成代码_通过UML类图,自动生成相关代码

    因为去年帮我的师父开发这个小软件,所以去年整了将近一个多月的时候,把这个基本的工程弄好了.姑且就叫做 版本1.0吧. 其实,功能很好理解,就是当你把UML(这里需要注意的是,只能用在VS,所以 ros ...

  2. matlab数组平方的计算自定义函数_从零开始的matlab学习笔记——(38)简单数论计算函数:取整,gcd,lcm,质数,全排列...

    matlab应用--求极限,求导,求积分,解方程,概率统计,函数绘图,三维图像,拟合函数,动态图,傅里叶变换,随机数,优化问题....更多内容尽在个人专栏:matlab学习 翻了翻优化工具箱,发现内容 ...

  3. python -- 计算 平方、乘方、平方根_从零开始学习PYTHON3讲义(二)把Python当做计算器...

    <从零开始PYTHON3>第二讲 上一讲我们说过了如何启动Python IDLE集成开发学习环境,macOS/Linux都可以在命令行执行idle3.Windows则从开始菜单中去寻找ID ...

  4. python做数学计算器_从零开始学习PYTHON3讲义(二)把Python当做计算器

    <从零开始PYTHON3>第二讲 上一讲我们说过了如何启动Python IDLE集成开发学习环境,macOS/Linux都可以在命令行执行idle3.Windows则从开始菜单中去寻找ID ...

  5. devc代码补全没效果_从零开始写文本编辑器(二十八):自动补全(上)

    前言 我本没打算这么早就写"自动补全"功能的. 但是在写XML资源编辑时,为了实现自动引用已有资源@string/xxx,需要一个合适的列表来让我选择.这样能防止拼写错误. 也就是 ...

  6. python鼠标移动事件_给turtle屏幕增加鼠标移动事件核心代码

    当鼠标移动时彩色方块会变色,标题栏会显示坐标值.以下是部分代码预览: """ 给turtle的屏幕增加鼠标移动事件. 本程序演示了如何给turtle.py模块增加鼠标移动 ...

  7. python黄金走势预测_使用python爬虫获取黄金价格的核心代码

    继续练手,根据之前获取汽油价格的方式获取了金价,暂时没钱投资,看看而已 #!/usr/bin/env python # -*- coding: utf-8 -*- """ ...

  8. 安卓 类微信开发(二)

    目录 一.主要内容 二.核心代码 三.效果展示 在UI的基础上: 安卓 类微信界面开发(一)_qingsongxyz的博客-CSDN博客 一.主要内容 对聊天主界面chatFragement进行完善, ...

  9. mybatis 无法初始化类_从零开始手写 mybatis(一)MVP 版本

    什么是 MyBatis ? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射. MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集. MyB ...

最新文章

  1. windows7环境下使用pip安装MySQLdb
  2. 他用几个公式解释了现金贷业务的风控与运营 (下) 2017-09-18 22:04 风控/运营/违约 “金额如此小的业务,成本极度敏感,刚开始的时候我们在数据成本和坏账成本之间特别纠结。” 以上是许
  3. word2vec原理之CBOW与Skip-Gram模型基础
  4. ASP.net远程调试笔记
  5. nyoj 710 外星人的供给站
  6. linux下简单限制网卡速度
  7. Fiddler绕过前端直接和后台进行交互
  8. 为什么说:“你不合适学Python?”醍醐灌顶!
  9. 操作系统原理(二)操作系统逻辑结构、CPU的态和中断机制
  10. JS控制浏览器捕捉键盘
  11. 切图工具:又一个处理大图的例子
  12. html的日期插件标签,CaretTab - 新式可以显示时间和日期的标签
  13. 在 react 中添加enter键出搜索
  14. 少室山论道——天下武功
  15. 如何在微信公众号文章中怎么添加附件?
  16. 保险行业的“偿二代”
  17. 汉字风格迁移篇---MF-NET一种新颖的少镜头风格化多语言字体生成
  18. 计算机flash听课记录范文,听课记录5篇
  19. H5(HTML)网页制作基础
  20. 在linux系统下运行jar包的命令如下:

热门文章

  1. [Toddler's Bottle]fd
  2. 明天要连续上班第7天
  3. SAP在采购和销售中的增值税处理
  4. KISS Dicom Viewer
  5. DICOM--如何判断是否压缩?
  6. 什么是循环?Java中有哪些循环?
  7. "北京成功故事"系列报道之四:巴伐利亚啤酒酿造师在北京
  8. 2021年烟花爆竹储存找解析及专业培训历年考试错题集锦
  9. GetClientRect,ClientToScreen,ClipCursor
  10. 随机生成4个数字php,PHP随机生成4位数字的方法