手把手教你使用Vue/React/Angular三大框架开发Pagination分页组件(下)丨【WEB前端大作战】

举报
Kagol 发表于 2021/04/20 21:29:12 2021/04/20
【摘要】 DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师。官方网站:devui.designNg组件库:ng-devui(欢迎Star)上一篇我们手把手教大家实现了Pagination组件的基本分页功能(只包含上一页/下一页),现在我们一起来看看如何实现分页组件最核心的分页器吧。6 分页器组件Pager我们再来回顾下分页...

 

DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师。
官方网站:devui.design
Ng组件库:ng-devui(欢迎Star)

 

Kagol.png

上一篇我们手把手教大家实现了Pagination组件的基本分页功能(只包含上一页/下一页),现在我们一起来看看如何实现分页组件最核心的分页器吧。

 

6 分页器组件Pager

我们再来回顾下分页组件的模块图:

中间显示页码的部分就是分页器,它的核心是页码显示和页码省略的逻辑。

6.1 页码显示策略

为了方便地跳转到任意页码,却又不至于在页面中显示太多页码,页码并不是始终全部显示出来的,而是在页码少时全部显示,页码多时只显示部分页码。这就存在显示策略问题。

我们从当前页码出发,比如模块图中当前页码是第5页:

那么以该页码为中心,两边显示一定的页码,比如两边各显示2页;

另外首页和尾页需要始终显示出来,方便回到首页和跳转到尾页;

首页到第3页中间的页码以及第7页到尾尾的页码都隐藏起来,并且支持点击左/右更多按钮,快捷跳转多页(比如5页)的功能。

另外需要考虑页码少的情况,如果只有8页怎么显示呢?

很简单,直接去掉右边的更多按钮就好:

如果当前页码在第4页呢?去掉左边的更多按钮,显示右边的更多按钮即可:

以上就是全部的页码显示策略。

现简述如下:

  1. 首页尾页需要始终显示出来(如果只有1页则不显示尾页);
  2. 除首尾页之外,当前页码左右最多只显示2页(共5页);
  3. 其他页码折叠起来,用更多按钮代替。

接下来看看如何用三大框架实现这个逻辑。

6.2 Vue版本

6.2.1 组件接口设计

编写Pager分页器组件之前,还是设计好组件的API:

  1. 总页数 - totalPage
  2. 默认当前页码 - defaultCurrent
  3. 页码改变事件 - 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. 第1步 实现首尾翻页
  2. 第2步 实现快捷分页
  3. 第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)">&lt;</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步:增加左/右更多按钮的翻页功能

有了首尾页的翻页还不够,还需要继续完善更多按钮的快捷翻页功能。

先梳理下更多按钮的显示逻辑:

  1. 中间按钮一共5页,加上首尾按钮2页,一共7页,也就是说只有大于7页,才有可能显示更多按钮;
  2. 左右更多按钮会随着当前页码的不同而显示或隐藏,以第4页和倒数第4页为界;
  3. 当页码大于第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共同决定,计算规则如下:

  1. 如果总页码小于等于7,则centerPages是除首尾页之外的所有页码;
  2. 如果总页码大于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分页器组件:

  1. 搭建基本模板框架
  2. 实现首尾页翻页
  3. 实现更多按钮快捷翻页
  4. 实现页码按钮组翻页

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还是有点区别的:

  1. React是直接用大括号{}包裹,然后像写JS一样写分支判断
  2. Vue在HTML元素中使用的是v-if指令进行分支判断

另外就是Vue中有标签class绑定的功能,而React没有类似的功能,需要通过在{}大括号中写三目运算符来判断高亮。

至此Pager已经可以直接拿去Pagination中使用了,不过只能首页和尾页翻页,接下来继续增强Pager的功能。

6.3.3 第2步:增加左/右更多按钮的翻页功能

更多按钮显示的逻辑和Vue版本一样:

  1. 只有大于7页,才有可能显示更多按钮;
  2. 左右更多按钮会随着当前页码的不同而显示或隐藏,以第4页和倒数第4页为界;
  3. 当页码大于第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的一样:

  1. 如果总页码小于等于7,则centerPages是除首尾页之外的所有页码;
  2. 如果总页码大于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>
    );
  })
}

列表渲染的方式需要注意⚠️:

  1. React依然使用的是大括号包裹,然后用JS的map方法进行迭代;
  2. 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. 第1步 实现首尾翻页
  2. 第2步 实现快捷分页
  3. 第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

 

《在瀑布下用火焰烤饼:三步法助你快速定位网站性能问题》

《从零搭建一个灰度发布环境》

 

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。