手把手教你使用Vue/React/Angular三大框架开发Pagination分页组件(下)丨【WEB前端大作战】
DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师。
官方网站:devui.design
Ng组件库:ng-devui(欢迎Star)
上一篇我们手把手教大家实现了Pagination组件的基本分页功能(只包含上一页/下一页),现在我们一起来看看如何实现分页组件最核心的分页器吧。
6 分页器组件Pager
我们再来回顾下分页组件的模块图:
中间显示页码的部分就是分页器,它的核心是页码显示和页码省略的逻辑。
6.1 页码显示策略
为了方便地跳转到任意页码,却又不至于在页面中显示太多页码,页码并不是始终全部显示出来的,而是在页码少时全部显示,页码多时只显示部分页码。这就存在显示策略问题。
我们从当前页码出发,比如模块图中当前页码是第5页:
那么以该页码为中心,两边显示一定的页码,比如两边各显示2页;
另外首页和尾页需要始终显示出来,方便回到首页和跳转到尾页;
首页到第3页中间的页码以及第7页到尾尾的页码都隐藏起来,并且支持点击左/右更多按钮,快捷跳转多页(比如5页)的功能。
另外需要考虑页码少的情况,如果只有8页怎么显示呢?
很简单,直接去掉右边的更多按钮就好:
如果当前页码在第4页呢?去掉左边的更多按钮,显示右边的更多按钮即可:
以上就是全部的页码显示策略。
现简述如下:
- 首页尾页需要始终显示出来(如果只有1页则不显示尾页);
- 除首尾页之外,当前页码左右最多只显示2页(共5页);
- 其他页码折叠起来,用更多按钮代替。
接下来看看如何用三大框架实现这个逻辑。
6.2 Vue版本
6.2.1 组件接口设计
编写Pager分页器组件之前,还是设计好组件的API:
- 总页数 - totalPage
- 默认当前页码 - defaultCurrent
- 页码改变事件 - onChange
6.2.2 基本模板框架
然后先写好模板,在Pager.vue的<template>中编写以下代码:
<template>
<ul class="x-pager">
<li class="number">1</li>
<li class="more left"></li>
<li class="number"></li>
<li class="more right"></li>
<li class="number">{{ totalPage }}</li>
</ul>
</template>
再在<script>中写基本的逻辑:
<script>
import Vue from 'vue';
export default {
name: 'Pager',
// 组件接口定义
props: {
totalPage: Number, // 总页数
defaultCurrent: Number, // 默认当前页码
},
};
</script>
搭好基本框架之后,我们采取最小可用产品(Minimum Viable Product,MVP)的思路:
分3步实现分页器功能:
- 第1步 实现首尾翻页
- 第2步 实现快捷分页
- 第3步 实现分页按钮组
6.2.3 第1步:首/尾页翻页逻辑
先显示第1步:首页尾页的显示和跳页逻辑:
首页
<li
class="number"
:class="{ active: this.current == 1 }"
@click="setPage(1)"
>1</li>
尾页
<li
class="number"
:class="{ active: this.current == totalPage }"
v-if="totalPage !== 1"
@click="setPage(totalPage)"
>{{ totalPage }}</li>
由于当前页码有可能从Pager组件外部改变(上一页/下一页按钮),因为需要监听defaultCurrent的变化,需要增加组件内部状态current代替defaultCurrent:
data() {
return {
current: this.defaultCurrent, // 当前页码
}
}
然后监听defaultCurrent,当外部传入的defaultCurrent发生变化时,将新值赋值给current:
watch: {
defaultCurrent: {
handler(newValue, oldValue) {
this.current = newValue;
}
}
}
接着定义翻页方法:
methods: {
setPage(page) {
// 对页码进行限制,不能超出[1, totalPage]的范围
let newPage = page;
if (page < 1) newPage = 1;
if (page > this.totalPage) newPage = this.totalPage;
this.current = newPage; // 设置当前页码
this.$emit('change', this.current); // 向外发射页码改变事件
}
}
显示的效果如下:
6.2.4 在Pagination组件中使用Pager组件
我们可以在Pagination组件中试试初版的Pager。
在Pagination.vue中,去掉之前页码显示的那一行代码,使用Pager组件替代:
<template>
<div class="m-pagination">
<Button class="btn-prev" @click="setPage(current - 1)"><</Button>
// 去掉该行 {{ current }},替换成以下Pager组件
<Pager :total-page="totalPage" :default-current="current" @change="onChange"></Pager>
<Button class="btn-next" @click="setPage(current + 1)">></Button>
</div>
</template>
然后增加Pager的onChange页码改变的回调事件:
methods: {
onChange(current) {
this.current = current; // 设置当前页码
this.$emit('change', this.current); // 向Pagination组件外发射页码改变事件
}
}
可以试试首/尾页的翻页效果:
6.2.5 第2步:增加左/右更多按钮的翻页功能
有了首尾页的翻页还不够,还需要继续完善更多按钮的快捷翻页功能。
先梳理下更多按钮的显示逻辑:
- 中间按钮一共5页,加上首尾按钮2页,一共7页,也就是说只有大于7页,才有可能显示更多按钮;
- 左右更多按钮会随着当前页码的不同而显示或隐藏,以第4页和倒数第4页为界;
- 当页码大于第4页时,应该显示左边更多按钮;
- 当页码小于倒数第4页,都应该显示右边更多按钮。
具体实现如下:
<!-- 左更多按钮 -->
<li
class="more left"
v-if="totalPage > 7 && current >= 5"
></li>
<!-- 右更多按钮 -->
<li
class="more right"
v-if="totalPage > 7 && current <= totalPage - 4"
></li>
不过我们不想写死这些数字,假设中间页码数为centerSize(这里是5),可以重构成:
<li
class="more left"
v-if="totalPage > centerSize + 2 && current >= centerSize"
></li>
<li
class="more right"
v-if="totalPage > centerSize + 2 && current <= totalPage - centerSize + 1"
></li>
接着是增加快捷翻页事件:
<li
class="more left"
v-if="totalPage > centerSize + 2 && current >= centerSize"
@click="setPage(current - jumpSize)"
></li>
<li
class="more right"
v-if="totalPage > centerSize + 2 && current <= totalPage - centerSize + 1"
@click="setPage(current - jumpSize)"
></li>
注意⚠️:为了不写死每次快捷跳转的页码,我们用jumpSize保存该值。
接下来我们可以看看快捷翻页的效果,为了清楚看出当前处于哪一页,我们暂时将中间为哦未实现的页码按钮组显示成当前页码:
<!-- 中间页码组 -->
<li class="number">{{ current }}</li>
初始在第1页:
点击右更多按钮之后(跳转到第6页):
再点击右更多按钮(跳转到第11页):
点击左更多按钮则又回到第6页,完美达到预期。
6.2.6 第3步:实现中间的页码按钮组
中间页码组centerPages是一个长度在[0, centerSize]之间的数组,它的值由总页码totalPage和当前页码current共同决定,计算规则如下:
- 如果总页码小于等于7,则centerPages是除首尾页之外的所有页码;
- 如果总页码大于7,则centerPages是以current为中心,左右各加两页组成的页码数组。
将centerPages定义为计算属性,具体实现如下:
computed: {
centerPages: function() {
// 中间页码计算
let centerPage = this.current;
if (this.current > this.totalPage - 3) {
centerPage = this.totalPage - 3;
}
if (this.current < 4) {
centerPage = 4;
}
if (this.totalPage <= this.centerSize + 2) {
// 总页码较小时,全部显示出来
const centerArr = [];
for (let i = 2; i < this.totalPage; i++) {
centerArr.push(i);
}
return centerArr;
} else {
// 总页码较大时,只显示中间centerSize个页码
const centerArr = [];
for (let i = centerPage - 2; i <= centerPage + 2; i++) {
centerArr.push(i);
}
return centerArr;
}
}
有了中间页码数组,就可以渲染中间页码组:
<!-- 中间页码组 -->
<li
class="number"
v-for="(page, index) in centerPages"
:key="index"
>{{ page }}</li>
接着为其增加active类(用于高亮)和绑定点击事件(用于跳转到相应的页码):
<!-- 中间页码组 -->
<li
class="number"
:class="{ active: current === page }"
v-for="(page, index) in centerPages"
:key="index"
@click="setPage(page)"
>{{ page }}</li>
最终效果如下:
只有1页的情况:
<=7页的情况:
>7页且当前页码<=4页的情况:
>7页且当前页码>4页的情况:
至此,Vue版本分页器组件已全部实现,整个Pagination组件也全部实现。
接下来看看React/Angular如何实现分页器吧。
6.3 React版本
同样采MVP的思路,我们按以下步骤开发Pager分页器组件:
- 搭建基本模板框架
- 实现首尾页翻页
- 实现更多按钮快捷翻页
- 实现页码按钮组翻页
6.3.1 基本模板框架
我们先搭建基本模板框架,在Pager.js中编写以下代码:
import React from 'react';
function Pager({ totalPage, defaultCurrent, onChange }) {
return (
<ul className="x-pager">
<li className="number">1</li>
<li className="more left"></li>
<li className="number"></li>
<li className="more right"></li>
<li className="number">{ totalPage }</li>
</ul>
);
}
export default Pager;
这只是一个空壳子,什么都做不了,接下来我们加点实际的功能。
6.3.2 第1步:首/尾页翻页逻辑
增加首尾页显示条件、高亮条件和翻页功能。
import React, { useState } from 'react';
function Pager({ totalPage, defaultCurrent, onChange }) {
// 使用useState定义内部状态:当前页码current
const [current, setPage] = useState(defaultCurrent);
return (
<ul className="x-pager">
<li
className={'number' + (current == 1 ? ' active' : '')}
onClick={() => {
setPage(1);
onChange(1);
}}
>1</li>
<li className="more left"></li>
<li className="number"></li>
<li className="more right"></li>
{ totalPage !== 1 && <li
className={'number' + (current == totalPage ? ' active' : '')}
onClick={() => {
setPage(totalPage);
onChange(totalPage);
}}
>{ totalPage }</li> }
</ul>
);
}
export default Pager;
值得注意的是条件渲染的写法,React和Vue还是有点区别的:
- React是直接用大括号{}包裹,然后像写JS一样写分支判断
- Vue在HTML元素中使用的是v-if指令进行分支判断
另外就是Vue中有标签class绑定的功能,而React没有类似的功能,需要通过在{}大括号中写三目运算符来判断高亮。
至此Pager已经可以直接拿去Pagination中使用了,不过只能首页和尾页翻页,接下来继续增强Pager的功能。
6.3.3 第2步:增加左/右更多按钮的翻页功能
更多按钮显示的逻辑和Vue版本一样:
- 只有大于7页,才有可能显示更多按钮;
- 左右更多按钮会随着当前页码的不同而显示或隐藏,以第4页和倒数第4页为界;
- 当页码大于第4页时,应该显示左边更多按钮;
- 当页码小于倒数第4页,都应该显示右边更多按钮。
左更多按钮:
const centerSize = 5; // 中间按钮组的页码数
const jumpSize = 5; // 快捷翻页的页数
{
totalPage > centerSize + 2 && current >= centerSize
&& <li className="more left"
onClick={() => {
setPage(current - jumpSize); // 设置快捷翻页后的新页码
onChange(current - jumpSize); // 页码改变时的外部回调事件
}}
></li>
}
右更多按钮:
{
totalPage > centerSize + 2 && current <= totalPage - centerSize + 1
&& <li className="more right"
onClick={() => {
setPage(current + jumpSize);
onChange(current + jumpSize);
}}
></li>
}
最后实现页码按钮组功能。
6.3.4 第3步:实现中间的页码按钮组
主要是需要计算好centerPages页码数组,计算逻辑和Vue的一样:
- 如果总页码小于等于7,则centerPages是除首尾页之外的所有页码;
- 如果总页码大于7,则centerPages是以current为中心,左右各加两页组成的页码数组。
先计算centerPages:
// 计算中间页码数组
const centerPages = [];
let centerPage = current;
if (current > totalPage - 3) {
centerPage = totalPage - 3;
}
if (current < 4) {
centerPage = 4;
}
if (totalPage <= centerSize + 2) {
for (let i = 2; i < totalPage; i++) {
centerPages.push(i);
}
} else {
for (let i = centerPage - 2; i <= centerPage + 2; i++) {
centerPages.push(i);
}
}
然后将其显示出来:
{
centerPages.map((page, index) => {
return (
<li
key={index}
className={'number' + (page == current ? ' active' : '')}
onClick={() => {
setPage(page);
onChange(page);
}}
>{ page }</li>
);
})
}
列表渲染的方式需要注意⚠️:
- React依然使用的是大括号包裹,然后用JS的map方法进行迭代;
- Vue是在HTML标签中使用v-for指令进行列表渲染。
由于Pager中的当前页码有可能通过外部改变(比如上一页/下一页按钮),因为在传入的defaultCurrent变化时,需要动态改变current,这需要借助另一个React Hook——useEffect——来实现,具体代码如下:
// 外部传入的defaultCurrent变化时,需要重新设置current
useEffect(() => {
setPage(defaultCurrent);
});
另外需要注意的就是更多按钮快捷翻页可能会越界,需要加以显示,为此我们编写了一个limitPage方法:
const limitPage = (page) => {
if (page < 1) return 1;
if (page > totalPage) return totalPage;
return page;
}
在更多按钮的事件中使用:
左更多按钮:
{
totalPage > centerSize + 2 && current >= centerSize
&& <li className="more left"
onClick={() => {
setPage(limitPage(current - jumpSize)); // 设置快捷翻页后的新页码
onChange(limitPage(current - jumpSize)); // 页码改变时的外部回调事件
}}
></li>
}
右更多按钮:
{
totalPage > centerSize + 2 && current <= totalPage - centerSize + 1
&& <li className="more right"
onClick={() => {
setPage(limitPage(current + jumpSize));
onChange(limitPage(current + jumpSize));
}}
></li>
}
这样就完成了React版本的Pager分页器组件,除了细微语法上的差异外,大部分代码逻辑都是一样的。
接下来即将介绍的Angular版本的Pager也是一样的,大部分逻辑都可以复用。
6.4 Angular版本
Angular实现Pager的思路和Vue/React也差不多,就是写法上的差异,同样按MVP的思路,分成以下3个步骤:
- 第1步 实现首尾翻页
- 第2步 实现快捷分页
- 第3步 实现分页按钮组
先实现首/尾页翻页功能。
6.4.1 第1步:实现首/尾页翻页逻辑
先做模板,在pager.component.html中编写以下代码:
<ul class="x-pager">
<li [ngClass]="{
number: true,
active: 1 == current
}"
(click)="setPage($event, 1)"
>1</li>
<li class="more left"></li>
<li class="number" ></li>
<li class="more right"></li>
<li *ngIf="totalPage !== 1" [ngClass]="{
number: true,
active: totalPage == current
}"
(click)="setPage($event, totalPage)"
>{{ totalPage }}</li>
</ul>
然后在pager.component.ts中写具体逻辑:
import { Component, Input, Output, EventEmitter } from "@angular/core";
@Component({
selector: 'x-pager',
templateUrl: './pager.component.html',
styleUrls: ['./pager.component.scss']
})
export class PagerComponent {
@Input() totalPage: number;
@Input() defaultCurrent: number;
@Output() onChange = new EventEmitter();
current = this.defaultCurrent;
setPage($event, page) {
this.current = page;
this.onChange.emit(this.current);
}
}
6.4.2 第2步:实现左/右更多按钮的翻页功能
由于用于设置页码的方法setPage前面已经写好了,因此只需要在模板中新加左/右更多按钮即可:
<li
class="more left"
*ngIf="totalPage > centerSize + 2 && current >= centerSize"
(click)="setPage($event, current - centerSize)"
></li>
<li
class="more right"
*ngIf="totalPage > centerSize + 2 && current <= totalPage - centerSize + 1"
(click)="setPage($event, current + centerSize)"
></li>
6.4.3 第3步:实现中间的页码按钮组
最后是实现页码按钮组,关键还是centerPages数组的计算,计算逻辑可以复用Vue/React的。具体实现如下:
@Input()
get centerPages() {
let centerPage = this.current;
if (this.current > this.totalPage - 3) {
centerPage = this.totalPage - 3;
}
if (this.current < 4) {
centerPage = 4;
}
const centerArr = [];
if (this.totalPage < this.centerSize + 2) {
for (let i = 2; i < this.totalPage; i++) {
centerArr.push(i);
}
} else {
for (let i = centerPage - 2; i <= centerPage + 2; i++) {
centerArr.push(i);
}
}
return centerArr;
}
类似Vue中的计算属性(computed)。
然后是使用centerPages渲染页码按钮组:
<li
[ngClass]="{
number: true,
active: page == current
}"
*ngFor="let page of centerPages"
(click)="setPage($event, page)"
>{{ page }}</li>
至此三大框架的Pager组件都已实现,因而Pagination组件也告一段落。
最后做一个总结,大致对比下Vue/React/Angular三大框架开发组件的差别。
框架 |
从外向内通讯 |
从内向外通讯 |
编程范式 |
列表渲染 |
条件渲染 |
事件绑定 |
内部状态 |
插槽定义方式 |
计算属性 |
监听外部传入的参数变量 |
Vue |
props |
$emit() |
响应式 |
v-for指令 |
v-if指令 |
v-bind:event(简写@event) |
data |
<slot> |
computed |
watch |
React |
props |
props |
函数组件 |
{}包裹map |
{}包裹三目运算符 |
onEvent |
useState |
props.children |
直接写 |
useEffect |
Angular |
@Input() |
@Output() emit() |
面向对象 |
*ngFor指令 |
*ngIf指令 |
(event) |
直接写 |
<ng-content> |
@Input() get |
ngOnChanges |
至此三大框架版本的Pagination分页组件就全部实现啦!
以上3大框架的Pagination组件源码地址:
https://github.com/kagol/components
本文参考DevUI分页组件写成,该组件源码地址:
https://github.com/DevCloudFE/ng-devui/tree/master/devui/pagination
欢迎大家关注DevUI组件库,给我们提意见和建议,也欢迎Star。
.
手把手教你使用Vue/React/Angular三大框架开发Pagination分页组件系列文章:
手把手教你使用Vue/React/Angular三大框架开发Pagination分页组件(上)丨【WEB前端大作战】
手把手教你使用Vue/React/Angular三大框架开发Pagination分页组件(中)丨【WEB前端大作战】
.
加入我们
我们是DevUI团队,欢迎来这里和我们一起打造优雅高效的人机设计/研发体系。招聘邮箱:muyang2@huawei.com。
文/DevUI Kagol
- 点赞
- 收藏
- 关注作者
评论(0)