
DevUI是一支兼具設計視角和工程視角的團隊,服務於華為雲DevCloud平台和華為內部數個中后台系統,服務於設計師和前端工程師。
官方網站:devui.design
Ng組件庫:ng-devui(歡迎Star)
引言
“他在正午、黃昏,在一天里的許多時刻去感受它、記錄它,結果也就讓我們看到了那么多的不同。他描繪它的角度沒變,但它的面目卻極大地改變了。”
19世紀著名的印象派畫家莫奈,喜歡對着同一處景物,分別畫出對象在不同時間,不同光線下的色彩變化。
比如不同季節的三株白楊:

比如一天中不同時刻的浮翁大教堂:

如果同一個組件,用不同的框架實現,會有什么不同呢?
帶着這個想法,我分別選用目前最火的Vue/React/Angular三大框架,去實現一個簡單的Pagination分頁組件。
1 組件需求
我們要實現的分頁組件大致效果如下:

主要包含以下功能:
- 點擊左右分頁按鈕可以跳轉到上一頁/下一頁;
- 點擊中間的頁碼按鈕可能跳轉到相應的頁碼;
- 首頁尾頁需要始終顯示出來(如果只有1頁則不顯示尾頁);
- 除首尾頁之外,當前頁碼左右最多只顯示2頁(共5頁);
- 頁碼太多時顯示更多頁碼按鈕,點擊更多頁碼按鈕跳轉5頁。
2 模塊設計
從設計稿可以看出,Pagination組件主要由2個模塊組成:
- Button - 左右分頁按鈕
- 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個組件文件:
- 按鈕組件 - Button.vue
- 分頁器組件 - Pager.vue
- 分頁組件 - 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個組件文件:
- 按鈕組件 - Button.js
- 分頁器組件 - Pager.js
- 分頁組件 - 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個組件文件:
- Pagination模塊 - pagination.module.ts
- 按鈕組件 - button.component.ts
- 分頁器組件 - pager.component.ts
- 分頁組件 - 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:
- List模塊 - list.module.ts
- 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的差別比較大:
- 一是外部傳參方式不同,Angular使用@Input這個裝飾器表示外部參數;
- 二是Angular使用ngFor指令渲染列表數據;
- 三是Angular優化DOM對比的方式是使用trackBy。
Angular組件的使用方式,倒是和其他框架大同小異:
<x-list [dataSource]="dataSource"></x-list>
5 基本分頁功能
接下來我們開始給Pagination組件添加實際的分頁功能。
添加分頁功能之前,我們先設計好Pagination組件的API:
- 數據總數 - total
- 每頁數據數 - defaultPageSize
- 當前頁碼 - defaultCurrent
- 頁碼改變事件 - onChange
total和defaultPageSize兩個參數可以合並為一個參數totalPage(總頁碼),不過考慮到后續的可擴展性(比如需要改變pageSize),將其拆分開來。
實現分頁按鈕分以下步驟:
- 實現一個通用的按鈕組件
- 在分頁組件中使用按鈕組件
- 使用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>
這里要特別注意的是:
- Vue組件向外暴露事件的方式:使用$emit方法;
- 還有就是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)"><</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實體字符<代替。
之前設計的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)"><</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" }
]
]
最終實現的分頁效果如下:

現在做一個小小的總結,為了實現分頁功能,我們:
- 先實現了一個通用的按鈕組件;
- 然后使用這個通用組件,在Pagination組件中增加上一頁/下一頁兩個翻頁按鈕,點擊可以改變當前頁碼current;
- 接着使用做好的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);
}}><</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);
}}><</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的差別是很明顯的:
- 一是綁定事件的語法不同;
- 二是定義插槽的方式不同;
- 三是暴露外部事件和發射外部事件的方式不同。
這里也簡單做一個對比:
| 綁定事件 |
定義插槽 |
外部事件 |
|
| 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)"
><</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的值,不再贅言。
只是有一些差異需要注意⚠️:
- Angular的初始化方法是ngOnInit,Vue是created;
- Angular綁定屬性的方式是使用中括號[],Vue是使用v-bind指令(或者簡寫方式:key)。
至此三大框架實現基本分頁功能的方法及其差異都已介紹完畢,后一節將介紹本文最核心的內容:分頁器的實現。
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 |
以上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
