手把手教你使用Vue/React/Angular三大框架開發Pagination分頁組件

 

 

DevUI是一支兼具設計視角和工程視角的團隊,服務於華爲雲DevCloud平臺和華爲內部數箇中後臺系統,服務於設計師和前端工程師。
官方網站:devui.design
Ng組件庫:ng-devui(歡迎Star)css

引言

「他在正午、黃昏,在一天裏的許多時刻去感覺它、記錄它,結果也就讓咱們看到了那麼多的不一樣。他描繪它的角度沒變,但它的面目卻極大地改變了。」html

19世紀著名的印象派畫家莫奈,喜歡對着同一處景物,分別畫出對象在不一樣時間,不一樣光線下的色彩變化。前端

好比不一樣季節的三株白楊:vue

好比一天中不一樣時刻的浮翁大教堂:node

若是同一個組件,用不一樣的框架實現,會有什麼不一樣呢?react

帶着這個想法,我分別選用目前最火的Vue/React/Angular三大框架,去實現一個簡單的Pagination分頁組件。git

 

1 組件需求

咱們要實現的分頁組件大體效果以下:github

主要包含如下功能:vue-cli

  1. 點擊左右分頁按鈕能夠跳轉到上一頁/下一頁;
  2. 點擊中間的頁碼按鈕可能跳轉到相應的頁碼;
  3. 首頁尾頁須要始終顯示出來(若是隻有1頁則不顯示尾頁);
  4. 除首尾頁以外,當前頁碼左右最多隻顯示2頁(共5頁);
  5. 頁碼太多時顯示更多頁碼按鈕,點擊更多頁碼按鈕跳轉5頁。

2 模塊設計

從設計稿能夠看出,Pagination組件主要由2個模塊組成:npm

  1. Button - 左右分頁按鈕
  2. Pager - 中間的分頁器

3 空的Pagination組件

咱們採用自上而下的方式建立組件,先建立一個空的Pagination組件。

注意⚠️

我使用的框架版本號以下:

node@10.15.1

vue-cli@3.7.0

vue@2.6.10

create-react-app@3.0.1

react@16.8.6

angular-cli@7.3.9

angular@7.2.0

3.1 Vue版本

使用Vue CLI建立一個基礎Vue項目,並輸入npm run serve命令啓動起來。

而後在components文件夾新建一個pagination文件夾,裏面新建咱們須要的3個組件文件:

  1. 按鈕組件 - Button.vue
  2. 分頁器組件 - Pager.vue
  3. 分頁組件 - Pagination.vue

在Pagination.vue文件中增長如下代碼:

<template>
  <div class="x-pagination">
    Pagination組件
  </div>
</template>

<script>
export default {
  name: 'Pagination',
};
</script>

Vue組件的特色是將HTML/CSS/JavaScript都統一放在一個.vue後綴的文件中。

對於習慣將HTML/CSS/JavaScript分開編寫的前端開發者來講,顯得很是天然,加上Vue的語法很是簡潔,入門門檻比較低,因此2014年一經推出,很快便席捲全球。

在views/Home.vue中使用Pagination組件:

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App by kagol"/>
    <Pagination />
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue';
import Pagination from '@/components/pagination/Pagination.vue';
export default {
  name: 'home',
  components: {
    HelloWorld,
    Pagination,
  },
};
</script>

組件的使用方式也和普通HTML元素很相似:

<Pagination />

須要注意的是使用Vue局部組件以前須要在components中聲明該組件。

這只是一個空組件,只顯示了「Pagination組件」文字,沒有太大的意義,不過不要着急,後面咱們會一步步完善該組件,實現咱們想要的功能,並能不斷擴展和演進。在繼續開發Vue版本的Pagination組件以前,咱們先來看看其餘框架如何實現和使用一個組件。

如下是顯示效果:

3.2 React版本

先來看看React框架,咱們一樣使用Create React App建立一個基礎的React項目,並輸入命令npm start命令啓動。

和Vue項目同樣,建立如下3個組件文件:

  1. 按鈕組件 - Button.js
  2. 分頁器組件 - Pager.js
  3. 分頁組件 - Pagination.js

在Pagination.js文件中增長如下代碼:

import React from 'react';

function Pagination() {
  return (
    <div className="x-pagination">
      Pagination組件
    </div>
  );
}

export default Pagination;

能夠看到React開發組件的方式和Vue相差很是大,React推崇函數式編程(FP,Functional Programming),每一個React組件都是一個函數,HTML/CSS/JavaScript都在函數裏面,在函數裏面返回模板內容。

須要注意⚠️的是在React中HTML元素的class須要寫成className,緣由是class是JavaScript中的保留關鍵字,而React使用的JSX是JavaScript的擴展,使用class會致使命名衝突。

React這種寫法很特別,初學者可能會不太習慣,不過一旦用習慣了,會以爲很是爽,以爲一切都很是合理,組件就應該這樣寫。

在App.js中使用Pagination組件:

import React from 'react';
import Pagination from './components/pagination/Pagination';
import './App.scss';

function App() {
  return (
    <div className="App">
      <Pagination />
    </div>
  );
}

export default App;

使用React組件的方式也很簡單,和使用普通HTML元素相似:

<Pagination />

顯示的效果與Vue版本無異。

3.3 Angular版本

和Vue/React這種專一View視圖層的輕量級框架不一樣,Angular是一個很重的框架,配備很是完整,Web開發過程當中你須要的一切,Angular框架都給你提供好了,你只須要隨手取用便可。

咱們一塊兒來看看怎麼開發一個Angular組件吧。

一樣是使用Angular CLI建立一個基礎的Angular項目,並輸入命令npm start命令啓動。

和React/Vue組件不一樣,Angular組件不能單獨使用,須要包一層Module,所以咱們須要建立1個模塊文件和3個組件文件:

  1. Pagination模塊 - pagination.module.ts
  2. 按鈕組件 - button.component.ts
  3. 分頁器組件 - pager.component.ts
  4. 分頁組件 - pagination.component.ts

HTML/CSS能夠放在ts文件裏面,也能夠放在單獨的文件裏。

通常而言,HTML/CSS內容較少時,會將它們放到ts文件裏。

先建立Pagination模塊,在pagination.module.ts文件中增長如下代碼:

import { NgModule } from "@angular/core";
@NgModule()
export class PaginationModule { }

而後是建立Pagination組件,在pagination.component.ts文件中增長如下代碼:

import { Component } from "@angular/core";
@Component({
  selector: 'x-pagination',
  template: `
    <div class="x-pagination">
      Pagination組件
    </div>
  `,
})
export class PaginationComponent { }

Angular和Vue/React很是明顯的區別已經顯示出來:

首先是組件須要依託於Module存在;

而後是無論是定義Module仍是Component都須要使用裝飾器;

好比定義一個Angular模塊須要使用@NgModule裝飾器,定義一個Angular組件須要使用@Component裝飾器。

還有就是Angular推崇的是面向對象的編程範式,Angular裏面的幾乎一切都是類和對象,除了剛纔一經介紹的模塊和組件,還有服務(Service)、管道(Pipe)等,都是類(class)。

爲了使用Pagination組件,咱們須要先導入Pagination模塊,並聲明Pagination組件,在app.module.ts文件中增長如下代碼:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { PaginationModule } from './components/pagination/pagination.module';
import { PaginationComponent } from './components/pagination/pagination.component';
@NgModule({
  declarations: [
    AppComponent,
    PaginationComponent, // 聲明Pagination組件
  ],
  imports: [
    BrowserModule,
    PaginationModule, // 導入Pagination模塊
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule

而後就能使用Pagination組件了,在app.component.ts文件中增長如下代碼:

<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<x-pagination></x-pagination>

使用Angular組件的方式和普通的HTML元素相似:

<x-pagination></x-pagination>

顯示的效果與Vue/React同樣。

4 List組件和假數據

在添加實際的分頁功能以前咱們須要先作一個List組件,用來模擬分頁數據的展現。

根據咱們以前介紹的3個框架實現組件的方式,而後稍微增長些額外的知識,咱們就能很快作一個數據渲染組件List。

仍是先看Vue框架吧。

4.1 Vue版本

新建List.vue組件文件,輸入如下代碼:

<template>
  <ul>
    <li v-for="list in lists" :key="list.id">
      {{ list.name }}
    </li>
  </ul>
</template>
<script>
export default {
  name: 'List',
  props: {
    dataSource: Array
  },
  data() {
    return {
      lists: this.dataSource
    }
  },
  watch: {
    // 對dataSource進行監聽,若是發生變化則從新將新值賦給lists
    dataSource: {
      handler(newValue, oldValue) {
        this.lists = newValue;
      }
    }
  }
};
</script>

在template模板部分,咱們使用Vue的v-for指令,在li元素中循環lists數組,並將name值顯示出來。其中的:key是v-bind:key的簡寫形式,爲元素綁定惟一的key值,用於DOM對比時的性能優化。

1) 經過props傳入數據

本來我打算直接將lists的值放到props中,經過外部傳進來,以下:

<template>
  <ul>
    <li v-for="list in lists" :key="list.id">
      {{ list.name }}
    </li>
  </ul>
</template>
<script>
export default {
  name: 'List',
  props: {
    lists: Array
  }
};
</script>

這樣有一個問題,就是外部傳入的lists若是發生變化,template裏綁定的lists不會相應的變化。

2) 維護內部狀態

爲了監聽props中的值的變化,我把lists放到組件內部狀態中(data),外部傳入的數據叫dataSource,以下:

<script>
export default {
  name: 'List',
  props: {
    dataSource: Array
  },
  data() {
    return {
      lists: this.dataSource
    }
  },
};
</script>

3) 監聽外部props的變化

而後監聽dataSource的變化,當dataSource變化時,將新值賦值給lists:

watch: {
  // 對dataSource進行監聽,若是發生變化則從新將新值賦給lists
  dataSource: {
    handler(newValue, oldValue) {
      this.lists = newValue;
    }
  }
}

傳入List組件的lists數組以下:

export const lists = [
  { id: 1, name: 'Curtis' },
  { id: 2, name: 'Cutler' },
  { id: 3, name: 'Cynthia' },
  { id: 4, name: 'Cyril' },
  { id: 5, name: 'Cyrus' },
  { id: 6, name: 'Dagmar' },
  { id: 7, name: 'Dahl' },
  { id: 8, name: 'Dahlia' },
  { id: 9, name: 'Dailey' },
  { id: 10, name: 'Daine' },
];

使用List組件展現數據:

<List :data-source="lists" />

這裏須要注意⚠️的是,全部綁定的數據須要使用短橫線命名法,好比上面的data-source,對應data中駝峯命名法的dataSource。

展現的效果以下:

4.2 React版本

React編寫的是函數組件,props的變化會直接反映到模板中,不須要單獨監聽,因此寫起來很是簡潔:

import React from 'react';
function List({ dataSource }) {
  return (
    <ul className="m-list">
      {
        dataSource.map(list => {
          return <li key={ list.id }>{ list.name }</li>;
        })
      }
    </ul>
  );
}
export default List

外部數據經過函數的props參數傳入,這裏將props進行了對象解構,直接取到了dataSource字段。

還有一點和Vue不太同樣,就是React是函數式編程的寫法,列表數據的渲染不須要v-for之類的指令,而是經過數組的map方法,直接返回相應的li元素便可,看着很是天然。其中li元素上綁定的key值與Vue中key值的做用相似。

使用方式和Vue的相似:

<List dataSource={dataSource} />

4.3 Angular版本

Angular稍微麻煩些,須要同時定義Module和Component:

  1. List模塊 - list.module.ts
  2. List組件:list.component.ts

先編寫list.module.ts:

import { NgModule } from "@angular/core";
@NgModule()
export class ListModule { }

而後編寫List組件list.component.ts:

import { Component, Input } from "@angular/core";
@Component({
  selector: 'x-list',
  template: `
    <ul>
      <li *ngFor="let list of dataSource; trackBy: trackByIndex">
        {{ list.name }}
      </li>
    </ul>
  `,
})
export class ListComponent {
  @Input() dataSource;
  trackByIndex(index, list){
    return list.id;
  }
}

Angular和Vue/React的差異比較大:

  1. 一是外部傳參方式不一樣,Angular使用@Input這個裝飾器表示外部參數;
  2. 二是Angular使用ngFor指令渲染列表數據;
  3. 三是Angular優化DOM對比的方式是使用trackBy。

Angular組件的使用方式,卻是和其餘框架大同小異:

<x-list [dataSource]="dataSource"></x-list>

5 基本分頁功能

接下來咱們開始給Pagination組件添加實際的分頁功能。

添加分頁功能以前,咱們先設計好Pagination組件的API:

  1. 數據總數 - total
  2. 每頁數據數 - defaultPageSize
  3. 當前頁碼 - defaultCurrent
  4. 頁碼改變事件 - onChange

total和defaultPageSize兩個參數能夠合併爲一個參數totalPage(總頁碼),不過考慮到後續的可擴展性(好比須要改變pageSize),將其拆分開來。

實現分頁按鈕分如下步驟:

  1. 實現一個通用的按鈕組件
  2. 在分頁組件中使用按鈕組件
  3. 使用Pagination組件對List進行分頁

5.1 Vue版本

5.1.1 實現通用的按鈕組件

經過前面編寫的空的Pagination組件和List組件,相信你們對Vue組件都很熟悉了。

新建一個Button.vue組件文件,編寫如下代碼:

<template>
  <button type="button" @click="$emit('click')"><slot></slot></button>
</template>
<script>
  export default {
    name: 'Button',
  };
</script>

這裏要特別注意的是:

  1. Vue組件向外暴露事件的方式:使用$emit方法;
  2. 還有就是Vue定義插槽的方式是使用<slot>標籤。

其實以上的寫法是一種簡寫形式,實際應該是這樣:

<template>
  <button type="button" @click="click()"><slot></slot></button>
</template>
<script>
  export default {
    name: 'Button',
    methods: {
      click() {
        this.$emit('click');
      }
    },
  };
</script>

$emit是Vue組件實例的是一個方法,用於組件對外暴露事件和傳遞數據,後面會看到傳參的例子。

5.1.2 在Pagination組件中使用Button組件

作了這麼多準備工做,終於能夠作些實際的功能。

還記得以前咱們編寫了一個空的Pagination組件嗎?這時咱們能夠往裏面寫點功能了。

<template>
  <div class="x-pagination">
    <Button class="btn-prev" @click="setPage(current - 1)">&lt;</Button>
    {{ current }}
    <Button class="btn-next" @click="setPage(current + 1)">></Button>
  </div>
</template>
<script>
import Button from './Button.vue';
export default {
  name: 'Pagination',
  components: {
    Button,
  },
  // 接口定義 props
  props: {
    defaultCurrent: Number,
    defaultPageSize: Number,
    total: Number,
  },
  // 組件內部狀態 data
  data() {
    return {
      current: this.defaultCurrent,
    }
  },
  // 計算屬性
  computed: {
    totalPage: function () {
      return Math.ceil(this.total / this.defaultPageSize);
    },
  },
  // 內部方法定義
  methods: {
    setPage(page) {
      if (page < 1) return;
      if (page > this.totalPage) return;
      this.current = page;
      this.$emit('change', this.current);
    },
  }
};
</script>

將以前的文字「Pagination組件」刪掉,加上上一頁(<)/下一頁(>)兩個翻頁按鈕,另外咱們也將當前頁碼current展現在兩個翻頁按鈕中間,這樣咱們能更清楚當前處於第幾頁。

因爲左尖括號與HTML標籤的左尖括號衝突,不能直接使用,須要使用HTML實體字符&lt;代替。

以前設計的Pagination組件的API參數都放到props裏面:

// 接口定義 props
props: {
  defaultCurrent: Number, // 默認當前頁碼
  defaultPageSize: Number, // 默認每頁數據數
  total: Number, // 數據總數
}

咱們定義了一個組件內部屬性current,用於存放動態的頁碼:

// 組件內部狀態 data
data() {
  return {
    current: this.defaultCurrent,
  }
}

須要注意⚠️的是,data屬性使用的是函數形式,在函數內部返回一個對象,current定義在該對象裏面,這樣能夠確保每一個實例能夠維護一份被返回對象的獨立的拷貝,具體緣由能夠參考官網的解釋

另外咱們還定義了一個計算屬性,用於獲取總頁碼totalPage(限制頁碼邊界時須要用到):

// 計算屬性
computed: {
  totalPage: function () {
    return Math.ceil(this.total / this.defaultPageSize);
  },
}

最後定義了一個內部方法setPage,用於改變頁碼:

// 內部方法定義
methods: {
  setPage(page) {
    if (page < 1) return; // 限制上一頁翻頁按鈕的邊界
    if (page > this.totalPage) return; // 限制下一頁翻頁按鈕的邊界
    this.current = page;
    this.$emit('change', this.current);
  },
}

當點擊上一頁/下一頁翻頁按鈕時都會調用該方法,傳入改變後的頁碼值。

若是是上一頁,則傳入current - 1:

<Button class="btn-prev" @click="setPage(current - 1)">&lt;</Button>

下一頁則是current + 1:

<Button class="btn-next" @click="setPage(current + 1)">></Button>

setPage中除了設置當前頁碼以外,還將頁碼改變事件發射出去,並將當前頁碼傳到組件外部。

this.$emit('change', this.current);

另外也增長了一些限制翻頁邊界的邏輯,避免翻頁時超過頁碼的邊界,致使沒必要要的Bug:

if (page < 1) return; // 限制上一頁翻頁按鈕的邊界
if (page > this.totalPage) return; // 限制下一頁翻頁按鈕的邊界

5.1.3 使用Pagination組件對List進行分頁

有了Pagination組件和List組件,就可使用Pagination對List進行分頁展現。

在Home.vue組件中使用Pagination組件。

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <List :data-source="dataList" />
    <Pagination :default-current="defaultCurrent" :default-page-size="defaultPageSize" :total="total" @change="onChange" />
  </div>
</template>
<script>
import Pagination from '@/components/pagination/Pagination.vue';
import List from './List.vue';
import { lists } from '@/db';
import { chunk } from '@/util';
export default {
  name: 'home',
  components: {
    Pagination,
    List,
  },
  data() {
    return {
      defaultCurrent: 1,
      defaultPageSize: 3,
      total: lists.length,
      dataList: [],
    }
  },
  created() {
    this.setList(this.defaultCurrent, this.defaultPageSize);
  },
  methods: {
    onChange(current) {
      this.setList(current, this.defaultPageSize);
    },
    setList: function(current, pageSize) {
      this.dataList = chunk(lists, pageSize)[current - 1];
    }
  }
};
</script>

除了defaultCurrent/defaultPageSize/total這3個Pagination組件的參數外,咱們在data內部狀態中還定義了一個dataList字段,用於動態傳入給List組件,達到分頁的效果。

在setList方法中將對lists進行分塊,並根據當前的頁碼獲取分頁數據,並賦值給dataList字段,這樣List組件中就會展現相應的分頁數據。

setList: function(current, pageSize) {
  this.dataList = chunk(lists, pageSize)[current - 1];
}

setList方法在兩處進行調用:created生命週期方法和onChange頁碼改變事件。

created生命週期事件在Vue實例初始化以後,掛載到DOM以前執行,在created事件中咱們將第1頁的數據賦值給dataList:

created() {
  this.setList(this.defaultCurrent, this.defaultPageSize);
}

所以List組件將展現第1頁的數據:

onChange事件是Pagination組件的頁碼改變事件,當點擊上一個/下一頁翻頁按鈕時執行,在該事件中可獲取到當前的頁碼current。

咱們在該事件中將當前頁碼的數據賦值給dataList,這樣List組件將展現當前頁碼的數據,從而達到分頁效果。

onChange(current) {
  this.setList(current, this.defaultPageSize);
}

setList方法調用了chunk方法(做用與Lodash中的chunk方法相似),該方法用於將一個數組分割成指定大小的多個小數組,它的源碼以下:

// 將數組按指定大小分塊
export function chunk(arr = [], size = 1) {
  if (arr.length === 0) return [];
  return arr.reduce((total, currentValue) => {
    if (total[total.length - 1].length === size) {
      total.push([currentValue]);
    } else {
      total[total.length - 1].push(currentValue);
    }
    return total;
  }, [[]]);
}

好比以前的lists數組,若是按每頁3條數據進行分塊chunk(lists, 3),則獲得的結果以下:

[
  [
    { "id": 1, "name": "Curtis" },
    { "id": 2, "name": "Cutler" },
    { "id": 3, "name": "Cynthia" }
  ],
  [
    { "id": 4, "name": "Cyril" },
    { "id": 5, "name": "Cyrus" },
    { "id": 6, "name": "Dagmar" }
  ],
  [
    { "id": 7, "name": "Dahl" },
    { "id": 8, "name": "Dahlia" },
    { "id": 9, "name": "Dailey" }
  ],
  [
    { "id": 10, "name": "Daine" }
  ]
]

最終實現的分頁效果以下:

如今作一個小小的總結,爲了實現分頁功能,咱們:

  1. 先實現了一個通用的按鈕組件;
  2. 而後使用這個通用組件,在Pagination組件中增長上一頁/下一頁兩個翻頁按鈕,點擊能夠改變當前頁碼current;
  3. 接着使用作好的Pagination組件對List列表組件進行分頁。

接下來咱們看下React如何實現以上功能。

5.2 React版本

5.1.1 實現通用的按鈕組件

一樣也是先定義一個通用按鈕組件Button.js:

import React from 'react';
function Button({ onClick, children }) {
  return (
    <button type="button" onClick={ onClick }>{ children }</button>
  );
}
export default Button

經過前面開發的Pagination/List組件,相信你們對React的函數組件並不陌生了。

和Vue不一樣的是,React不須要對外發射事件之類的操做,傳什麼事件進來直接就發射出去了;

另外一個不一樣是定義插槽的方式,React使用props.children表明組件標籤中間傳入的內容。

5.1.2 在Pagination組件中使用Button組件

而後使用通用按鈕組件,在Pagination組件中增長上一頁/下一頁兩個翻頁按鈕:

import React, { useState } from 'react';
import Button from './Button';
function Pagination(props) {
  const { total, defaultCurrent, defaultPageSize, onChange } = props;
  // 聲明一個叫 「current」 的 state 變量,用來保存當前的頁碼;
  // setPage方法是用來改變current的。
  const [current, setPage] = useState(defaultCurrent);
  const totalPage = Math.ceil(total / defaultPageSize);
  return (
    <div className="m-pagination">
      <Button className="btn-prev" onClick={() => {
        if (current < 2) return;
        setPage(current - 1);
        onChange(current - 1);
      }}>&lt;</Button>
      {{ current }}
      <Button className="btn-next" onClick={() => {
        if (current >= totalPage) return;
        setPage(current + 1);
        onChange(current + 1);
      }}>></Button>
    </div>
  );
}
export default Pagination;

這裏引出React 16.8以後一個很重要的概念:React Hooks

爲了在函數組件中定義組件內部狀態,從react庫中引入了useState這個方法:

import React, { useState } from 'react';

useState就是一個Hook,經過在函數組件裏調用它來給組件添加一些內部state,React會在重複渲染時保留這個state。

useState會返回一對值:當前狀態和一個讓你更新它的函數。

useState惟一的參數就是初始state,這裏是默認當前頁碼(defaultCurrent),這個初始 state 參數只有在第一次渲染時會被用到。

const [current, setPage] = useState(defaultCurrent);

當點擊上一頁/下一頁翻頁按鈕時,咱們調用了setPage方法,傳入新的頁碼,從而改變current當前頁碼,實現分頁功能。

另外也和Vue版本同樣,經過調用onChange方法將頁碼改變事件發射出去,並將當前頁碼傳遞到組件以外。

若是是上一頁:

<Button className="btn-prev" onClick={() => {
  if (current < 2) return;
  setPage(current - 1);
  onChange(current - 1);
}}>&lt;</Button>

若是是下一頁:

<Button className="btn-next" onClick={() => {
  if (current >= totalPage) return;
  setPage(current + 1);
  onChange(current + 1);
}}>></Button>

5.1.3 使用Pagination組件對List進行分頁

Pagination組件作好了,咱們就可使用它來給List列表組件進行分頁啦。

在App.js中引入List和Pagination組件:

import React, { useState } from 'react';
import Pagination from './components/pagination/Pagination';
import List from './components/List';
import { lists } from './db';
import { chunk } from './util';
import './App.scss';
function App() {
  const defaultCurrent = 1;
  const defaultPageSize = 3;
  // 設置List默認分頁數據:第一頁的數據chunk(lists, defaultPageSize)[defaultCurrent - 1]
  const [dataSource, setLists] = useState(chunk(lists, defaultPageSize)[defaultCurrent - 1]);
  return (
    <div className="App">
      <List dataSource={dataSource} />
      <Pagination total={lists.length} defaultCurrent={defaultCurrent} defaultPageSize={defaultPageSize} onChange={current => {
        // 頁碼改變時,從新設置當前的分頁數據
        setLists(chunk(lists, defaultPageSize)[current - 1]);
      }} />
    </div>
  );
}
export default App;

一樣也是定義了一個List組件的數據源(使用useState這個React Hook):dataSource,默認設置爲第一頁的數據:

// 設置List默認分頁數據:第一頁的數據chunk(lists, defaultPageSize)[defaultCurrent - 1]
const [dataSource, setLists] = useState(chunk(lists, defaultPageSize)[defaultCurrent - 1]);

當頁碼改變時,Pagination的onChange事件能捕獲到並執行,該事件中能夠拿到當前頁碼current,這時咱們能夠經過調用useState的第2個返回值——setLists方法——來改變dataSource數據源,實現分頁功能:

<Pagination ... onChange={current => {
  // 頁碼改變時,從新設置當前的分頁數據
  setLists(chunk(lists, defaultPageSize)[current - 1]);
}} />

在組件內維護狀態的方式,React和Vue相差較大,這裏作一個簡單的對比:

 

 

組件內部狀態存放位置

改變組件內部狀態的方式

React

useState第1個返回值。

const [state, setState] = useState(initialState];

useState第2個返回值(一個方法)。

const [state, setState] = useState(initialState];

Vue

data方法中。

data() {

   return {

     state: [],

   }

}

methods對象中。

methods: {

   setState: function() {

     // 執行具體的代碼

   }

}

另外還有一個須要注意⚠️:

在Vue中,爲了初始化List的數據源,無法直接在data中寫,好比:

data() {
  return {
    dataList: chunk(lists, this.defaultPageSize)[this.defaultCurrent - 1],
  }
}

而是必須在created初始化方法中寫:

created() {
  this.dataList = chunk(lists, this.defaultPageSize)[this.defaultCurrent - 1];
}

而在React中則顯得簡潔和天然許多:

// 設置List默認分頁數據:第一頁的數據
const [dataSource, setLists] = useState(chunk(lists, defaultPageSize)[defaultCurrent - 1];

不過React這種寫法對初學者是不友好的,習慣以後會以爲很舒服。

5.3 Angular版本

5.1.1 實現通用的按鈕組件

最後來看下Angular如何實現分頁功能,思路都同樣,先定義一個通用按鈕組件button.component.ts:

import { Component, Output, EventEmitter } from "@angular/core";
@Component({
  selector: 'x-button',
  template: `
    <button type="button" (click)="onClick()"><ng-content></ng-content></button>
  `,
})
export class ButtonComponent {
  @Output() btnClick = new EventEmitter();
  onClick() {
    this.btnClick.emit();
  }
}

Angular和React/Vue的差異是很明顯的:

  1. 一是綁定事件的語法不一樣;
  2. 二是定義插槽的方式不一樣;
  3. 三是暴露外部事件和發射外部事件的方式不一樣。

這裏也簡單作一個對比:

 

 

綁定事件

定義插槽

外部事件

Vue

v-on指令(簡寫形式:@)

<slot>標籤

$emit()

React

props傳遞

props.onClick

props.children

props傳遞,無需發射

Angular

括號符()

(click)="btnClick()"

<ng-content>標籤

@Output()+emit()

5.1.2 在Pagination組件中使用Button組件

如今模板中使用通用按鈕組件pagination.component.html:

<div class="x-pagination">
  <x-button
    class="btn-prev"
    (btnClick)="setPage(current - 1)"
  >&lt;</x-button>
  {{ current }}
  <x-button
    class="btn-next"
    (btnClick)="setPage(current + 1)"
  >></x-button>
</div>

而後在pagination.component.ts中定義具體邏輯:

import { Component, Input, Output, EventEmitter } from "@angular/core";
@Component({
  selector: 'x-pagination',
  templateUrl: './pagination.component.html',
  styleUrls: ['./pagination.component.scss']
})
export class PaginationComponent {
  // 組件接口定義
  @Input() total: number;
  @Input() defaultCurrent = 1;
  @Input() defaultPageSize: number;
  @Output() onChange = new EventEmitter();
  // 計算屬性
  @Input()
  get totalPage() {
    return Math.ceil(this.total / this.defaultPageSize);
  }
  // 組件內部狀態
  current = this.defaultCurrent;
  // 組件方法
  setPage(page) {
    if (this.current < 2) return;
    if (this.current > this.totalPage - 1) return;
    this.current = page;
    this.onChange.emit(this.current);
  }
}

和Vue/React同樣,定義組件接口/計算屬性/內部狀態/組件方法,只是具體的語法不一樣,語法上的對比前面已經說明,再也不贅言。

下面直接介紹如何使用Pagination組件對List進行分頁。

5.1.3 使用Pagination組件對List進行分頁

在app.component.html中引入Pagination/List兩個組件:

<x-list [dataSource]="dataSource"></x-list>
<x-pagination
  [total]="total"
  [defaultCurrent]="defaultCurrent"
  [defaultPageSize]="pageSize"
  (onChange)="onChange($event)"
></x-pagination>

在app.component.ts中定義具體邏輯:

import { Component, OnInit } from '@angular/core';
import { lists } from './db';
import { chunk } from './util';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  defaultCurrent = 1;
  defaultPageSize = 3;
  total = lists.length;
  dataSource = [];
  ngOnInit() {
    this.setLists(this.defaultCurrent, this.defaultPageSize);
  }
  onChange(current) { // 頁碼改變
    this.setLists(current, this.defaultPageSize);
  }
  setLists(page, pageSize) {
    this.dataSource = chunk(lists, pageSize)[page - 1];
  }
}

思路也是同樣的,定義一個List組件的數據源dataSource,組件初始化(ngOnInit)時給dataSource設置初始分頁數據(第一頁數據),而後在頁碼改變時從新設置dataSource的值,再也不贅言。

只是有一些差別須要注意⚠️:

  1. Angular的初始化方法是ngOnInit,Vue是created;
  2. Angular綁定屬性的方式是使用中括號[],Vue是使用v-bind指令(或者簡寫方式:key)。

至此三大框架實現基本分頁功能的方法及其差別都已介紹完畢,後一節將介紹本文最核心的內容:分頁器的實現。

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

 

以上3大框架的Pagination組件源碼地址:

https://github.com/kagol/components

 

本文參考DevUI分頁組件寫成,該組件源碼地址:

https://github.com/DevCloudFE/ng-devui/tree/master/devui/pagination

 

歡迎你們關注DevUI組件庫,給咱們提意見和建議,也歡迎Star。

 

加入咱們

咱們是DevUI團隊,歡迎來這裏和咱們一塊兒打造優雅高效的人機設計/研發體系。招聘郵箱:muyang2@huawei.com。

文/DevUI Kagol

 

相關文章
相關標籤/搜索