前言
在平时使用element ui 的时候,通过后台返回数据进行列表渲染的操作非常多。每次用到的组件无非是 el-table相关的组件。当数据渲染时,还有一个很重要的操作就是分页,当然element ui提供了el-pagination组件去实现这个功能。
el-pagination官网效果 (完整功能)
日常使用
首先来看一下平时时如何使用的:
<el-pagination
class="pagination"
background
layout="total, prev, pager, next, sizes"
:page-sizes="[15, 20, 30]"
:total="total"
:current-page.sync="current_page"
@size-change="eventSizeChange"
@current-change="eventCurrentChange"
></el-pagination>
data () {
total:0,
current_page:1,
params: {
page:1,
page_size:20
}
}
methods () {
//假设是一个请求接口的函数
getList() {
getList(this.params) {
//处理数据
}
}
// 分页
eventCurrentChange (page) {
this.params.page = page
this.getList()
},
eventSizeChange (size) {
this.params.page_size = size
this.params.page = 1
this.current_page = 1
this.getList()
}
}
当你把这个两个分页参数传给后端的时候,后端会进行处理,把需要的数据传回给你。
而项上面官网所展示的分页组件,element ui对其封装又是如何思考的呢?
下面我们一起来看看吧
学习目标时能够理解文章末尾的一些知识点
源码目录
首先理清两个文件的大体内容:
我就直接用思维导图先描述一下:
文件一、pangination.js
就是对整个组件的封装
包括总页数,每页条数,按钮跳转,输入跳转等等
先分析如何将el-pagination封装 src/pangination.js
1、Props
在pangination.js中,作为props使用
props: {
//每页显示条目个数,支持 .sync 修饰符
pageSize: {
type: Number,
default: 10
},
//是否使用小型分页样式
small: Boolean,
// 总条目数
total: Number,
//总页数,total 和 page-count 设置任意一个就可以达到显示页码的功能;如果要支持 page-sizes 的更改,则需要使用 total 属性
pageCount: Number,
//页码按钮的数量,当总页数超过该值时会折叠
pagerCount: {
type: Number,
validator(value) {
return (value | 0) === value && value > 4 && value < 22 && (value % 2) === 1;
},
default: 7
},
// 当前页数,支持 .sync 修饰符
currentPage: {
type: Number,
default: 1
},
//组件布局,子组件名用逗号分隔
layout: {
default: 'prev, pager, next, jumper, ->, total'
},
//每页显示个数选择器的选项设置
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 40, 50, 100];
}
},
popperClass: String,
prevText: String,
nextText: String,
background: Boolean,
disabled: Boolean,
//只有一页时是否隐藏
hideOnSinglePage: Boolean
},
2、render
当然这里面涉及了很多组件prev, pager, next, jumper, ->, total,我们等下再说
render(h) {
//this.layout就是之前我们再props里面定义的组件布局,如果每页子组件那么就显示null
const layout = this.layout;
if (!layout) return null;
//处理只有一页是否隐藏
if (this.hideOnSinglePage && (!this.internalPageCount || this.internalPageCount === 1)) return null;
// template 是一个父容器模板,定义的时候可以先处理它的样式,包括背景以及小样式设定
let template = <div class={['el-pagination', {
'is-background': this.background,
'el-pagination--small': this.small
}] }></div>;
//TEMPLATE_MAP 是一个组件集合,里面涉及的组件会在后面定义
const TEMPLATE_MAP = {
prev: <prev></prev>, // 上一页
jumper: <jumper></jumper>, // 跳转 前往多少页
pager: <pager currentPage={ this.internalCurrentPage } pageCount={ this.internalPageCount } pagerCount={ this.pagerCount } on-change={ this.handleCurrentChange } disabled={ this.disabled }></pager>,
next: <next></next>, // 下一页
sizes: <sizes pageSizes={ this.pageSizes }></sizes>, // 每页显示条目个数
slot: <slot>{ this.$slots.default ? this.$slots.default : '' }</slot>,
total: <total></total> // 总共的页数
};
//
const components = layout.split(',').map((item) => item.trim());
const rightWrapper = <div class="el-pagination__rightwrapper"></div>;
let haveRightWrapper = false;
template.children = template.children || [];
rightWrapper.children = rightWrapper.children || [];
// // ->这个符号主要是将其后面的组件放在rightWrapper中,然后右浮动;如果存在->符号,就将haveRightWrapper为true
components.forEach(compo => {
if (compo === '->') {
haveRightWrapper = true;
return;
}
// 当haveRightWrapper为true,即在->后面的放入rightWrapper中
if (!haveRightWrapper) {
template.children.push(TEMPLATE_MAP[compo]);
} else {
rightWrapper.children.push(TEMPLATE_MAP[compo]);
}
});
if (haveRightWrapper) {
template.children.unshift(rightWrapper);
}
return template;
},
3、methods :
- handleCurrentChange(val)
- prev()
- next()
- getValidCurrentPage(value)
- emitChange()
4、computed:
5、watch:
6、components 组件:
prev
上一页组件 就是点击'上一页'或者前进按钮 (以及边界处理)
render(h) {
return (
<button
type="button"
class="btn-prev"
disabled={ this.$parent.disabled || this.$parent.internalCurrentPage <= 1 }
on-click={ this.$parent.prev }>
{
this.$parent.prevText
? <span>{ this.$parent.prevText }</span>
: <i class="el-icon el-icon-arrow-left"></i>
}
</button>
);
}
next
下一页操作,和prev类似
render(h) {
return (
<button
type="button"
class="btn-next"
disabled={ this.$parent.disabled || this.$parent.internalCurrentPage === this.$parent.internalPageCount || this.$parent.internalPageCount === 0 }
on-click={ this.$parent.next }>
{
this.$parent.nextText
? <span>{ this.$parent.nextText }</span>
: <i class="el-icon el-icon-arrow-right"></i>
}
</button>
);
}
sizes
这个组件涉及的东西比较多,先总览一下
- mixins: [Locale],
- props
props: {
pageSizes: Array
},
- watch immediate:true,代表watch里面声明了之后会立马执行handler里面的函数。
watch: {
pageSizes: {
immediate: true,
handler(newVal, oldVal) {
if (valueEquals(newVal, oldVal)) return;
if (Array.isArray(newVal)) {
this.$parent.internalPageSize = newVal.indexOf(this.$parent.pageSize) > -1
? this.$parent.pageSize
: this.pageSizes[0];
}
}
}
},
- render
也就是需要渲染出这个样式:
render(h) {
return (
<span class="el-pagination__sizes">
<el-select
value={ this.$parent.internalPageSize }
popperClass={ this.$parent.popperClass || '' }
size="mini"
on-input={ this.handleChange }
disabled={ this.$parent.disabled }>
{
this.pageSizes.map(item =>
<el-option
value={ item }
label={ item + this.t('el.pagination.pagesize') }>
</el-option>
)
}
</el-select>
</span>
);
},
- components
components: {
ElSelect,
ElOption
},
- methods
methods: {
handleChange(val) {
if (val !== this.$parent.internalPageSize) {
this.$parent.internalPageSize = val = parseInt(val, 10);
this.$parent.userChangePageSize = true;
this.$parent.$emit('update:pageSize', val);
this.$parent.$emit('size-change', val);
}
}
}
jumper
mixins: [Locale],
components: { ElInput },
data() {
return {
userInput: null
};
},
watch: {
'$parent.internalCurrentPage'() {
this.userInput = null;
}
},
methods: {
handleKeyup({ keyCode, target }) {
// Chrome, Safari, Firefox triggers change event on Enter
// Hack for IE: https://github.com/ElemeFE/element/issues/11710
// Drop this method when we no longer supports IE
if (keyCode === 13) {
this.handleChange(target.value);
}
},
handleInput(value) {
this.userInput = value;
},
handleChange(value) {
this.$parent.internalCurrentPage = this.$parent.getValidCurrentPage(value);
this.$parent.emitChange();
this.userInput = null;
}
},
render(h) {
return (
<span class="el-pagination__jump">
{ this.t('el.pagination.goto') }
<el-input
class="el-pagination__editor is-in-pagination"
min={ 1 }
max={ this.$parent.internalPageCount }
value={ this.userInput !== null ? this.userInput : this.$parent.internalCurrentPage }
type="number"
disabled={ this.$parent.disabled }
nativeOnKeyup={ this.handleKeyup }
onInput={ this.handleInput }
onChange={ this.handleChange }/>
{ this.t('el.pagination.pageClassifier') }
</span>
);
}
},
total
mixins: [Locale],
render(h) {
return (
typeof this.$parent.total === 'number'
? <span class="el-pagination__total">{ this.t('el.pagination.total', { total: this.$parent.total }) }</span>
: ''
);
}
},
Pager // 单独分出来
文件二、 pager.vue
<template>
<ul @click="onPagerClick" class="el-pager">
<li
:class="{ active: currentPage === 1, disabled }"
v-if="pageCount > 0"
class="number">1</li>
<!-- 显示第一页 -->
<!-- 如果当前页数为1,那么样式为显蓝色,且按钮为禁用 -->
<!-- 总页数大于0的时候才会显示哦 -->
<li
class="el-icon more btn-quickprev"
:class="[quickprevIconClass, { disabled }]"
v-if="showPrevMore"
@mouseenter="onMouseenter('left')"
@mouseleave="quickprevIconClass = 'el-icon-more'">
<!-- 显示向右更多 没有移上去就是...,移上去就是一个向右的图标 -->
<!-- quickprevIconClass 往前的图标 -->
<!-- showPrevMore 这个为真的时候才显示,因为有时候页数没达到那么多,...就不会显示,因此下面需要判断 -->
<!-- 两个移入移除的操作就是样式改变了 -->
</li>
<li
v-for="pager in pagers"
:key="pager"
:class="{ active: currentPage === pager, disabled }"
class="number">{{ pager }}</li>
<!-- 控制要显示哪些页数 -->
<!-- pager是一个计算属性 -->
<li
class="el-icon more btn-quicknext"
:class="[quicknextIconClass, { disabled }]"
v-if="showNextMore"
@mouseenter="onMouseenter('right')"
@mouseleave="quicknextIconClass = 'el-icon-more'">
<!-- 显示向右更多 没有移上去就是...,移上去就是一个向右的图标 -->
</li>
<li
:class="{ active: currentPage === pageCount, disabled }"
class="number"
v-if="pageCount > 1">{{ pageCount }}</li>
<!-- 显示总页数 -->
</ul>
</template>
通过上面简单的说明,下面我们一起来看看需要了解的几个方法:
1、onPagerClick() 这个方法被绑定在ul上,而不是li标签上,使用到的是事件代理模式
DOM操作是十分消耗性能的,所以重复的事件绑定简直是性能杀手。而事件代理的核心思想,就是通过尽量少的绑定,去监听尽量多的事件。虽然这里只有五个li标签,但是通过事件代理,也能减少一定的性能消耗
onPagerClick(event) {
const target = event.target;
if (target.tagName === 'UL' || this.disabled) {
return;
}
let newPage = Number(event.target.textContent);
const pageCount = this.pageCount;//总页数
const currentPage = this.currentPage; // 当前页数
const pagerCountOffset = this.pagerCount - 2; // 出去前后按钮剩下的
if (target.className.indexOf('more') !== -1) {
if (target.className.indexOf('quickprev') !== -1) {
newPage = currentPage - pagerCountOffset;
} else if (target.className.indexOf('quicknext') !== -1) {
newPage = currentPage + pagerCountOffset;
}
}
/* istanbul ignore if */
if (!isNaN(newPage)) {
if (newPage < 1) {
newPage = 1;
}
if (newPage > pageCount) {
newPage = pageCount;
}
}
if (newPage !== currentPage) {
this.$emit('change', newPage);
}
},
2、showPrevMore showNextMore
由四个图可以看出,more按钮出现的情况有四种
- 没有more
- 左右都有more
- 左边有,右边没有
- 右边有,左边没有
那么又是怎么实现的呢?
因此这边需要处理两个问题,一个是监听watch,当存在有more的情况,要显示...的图标样式
(下面都简称为more)。一个是计算computed,计算是否需要more,该如何用。
- 先来看watch吧,简单一些:
watch: {
showPrevMore(val) {
if (!val) this.quickprevIconClass = 'el-icon-more';
},
showNextMore(val) {
if (!val) this.quicknextIconClass = 'el-icon-more';
}
},
- computed
了解清楚变量的意思,下面这段代码就是实现刚才的四种情况 pagerCount 就是要显示的按钮数量 halfPagerCount 一半 currentPage 当前页数 pageCount 总页数
下面我就拿一个左右都有的例子来理一下这个思路:
pagers() {
const pagerCount = this.pagerCount; //11
const halfPagerCount = (pagerCount - 1) / 2; //5
const currentPage = Number(this.currentPage); // 7
const pageCount = Number(this.pageCount); // 50
let showPrevMore = false;
let showNextMore = false;
//判断是否有more
if (pageCount > pagerCount) { // 50 > 11
if (currentPage > pagerCount - halfPagerCount) {
// 7 > 11 - 5
showPrevMore = true;
}
if (currentPage < pageCount - halfPagerCount) {
// 7 < 50 - 5
showNextMore = true;
}
}
const array = [];
// 如果有more的话,具体是四种情况的哪一种
if (showPrevMore && !showNextMore) {
const startPage = pageCount - (pagerCount - 2);
for (let i = startPage; i < pageCount; i++) {
array.push(i);
}
} else if (!showPrevMore && showNextMore) {
for (let i = 2; i < pagerCount; i++) {
array.push(i);
}
} else if (showPrevMore && showNextMore) {
// offset 4
const offset = Math.floor(pagerCount / 2) - 1;
// for (let i = 3;i <= 11;i++)
for (let i = currentPage - offset ; i <= currentPage + offset; i++) {
array.push(i);
}
} else {
for (let i = 2; i < pageCount; i++) {
array.push(i);
}
}
this.showPrevMore = showPrevMore;
this.showNextMore = showNextMore;
return array;
}
总结所学
目录结构组织
混入 (mixin)
利用事件代理提高性能
watch immediate: true,
computed
文章目前还未完整写全,将会持续更新