山地人

第011期.前端问答-如何提高组件重用能力?

山地人
山地人
2021-05-13

上一期,我们聊了如何做分页的功能,但是如果你的项目中有很多地方要用到分页功能。

你是否会觉得这样的写法重复性很高,作为有追求的程序员,你一定不希望把重复的代码码上10遍

所以这一期,我们来看下,如何把这个业务中用到的分页功能抽取成一个通用的分页组件

我们会从 业务分析——思路整理——代码实现。分三个阶段,循序渐进的来解决这个问题。

art-book-pages-browse-256467

分析分页功能

要封装一个可以复用的分页组件,我们先要分析清楚分页功能在不同的业务中,都有哪些共同的地方差异的地方

我们先考虑下分页功能里都有哪些要素。

表格

  1. 对于常用的纯展示型的表格,可能不同的业务中,只是表格里的每一列取的数据字段不同,其他部分都是基本相同的
  2. 对于那种带操作的表格,可能每一列中还会有不同的操作按钮,这个表格不同业务的差异还是很大的。

底部分页条

在我们的一个项目里,通常采用的分页控件,都是比较相似的。所以,我们可以把这些分页条抽取到一个通用的组件中去。

设计组件

通过上面的分析,我们可以归纳出两种类型的分页组件。一种是展示常规数据的分页组件<TableWithPagination/>,另一类是展示不同数据类型的分页容器<PaginationContainer/>

现在,我们再来看看,我们对外要暴露哪些接口。这些暴露在外部的接口,通常就是业务中需要变化的部分。而业务中一些共性的部分,我们可以封装到组件的内部。这样就达到了复用的效果。

对外的接口——组件的Props属性

这里我们依然使用大家常用的Vue来讲解,我们每一个分页组件的Table部分,都需要知道要展示的数据是什么(tableData)。

另外,我们需要告诉我们的分页组件,每一页显示几条数据(pageSize),一共有多少条数据(total),已经当前展示的是第几页(currentPage)。

props:{
tableData:Array,
total:{
default:0,
type:Number,
},
currentPage:{
default:0,
type:Number,
},
pageSize:{
default:2,
type:Number,
},
}

然后,我们再来思考一下,当我们点击了底部的分页按钮后,我们需要重新发送请求,然后把数据更新到我们的展示组件中,所以这里我们又需要一个对外暴露的方法。

getData:{
type:Function,
}

getData(currentPage)方法,在每次点击分页上的翻页按钮的时候,拿到当前要展示的currentPage,然后去服务器拉取最新的数据。最后更新到我们组件的tableData中去。

上面这些是<TableWithPagination/><PaginationContainer/>两个组件共同的地方。

然后我们再来说这两个组件差异的地方:

TableWithPagination

img

需要知道要展示哪些列,所以需要对外提供一个接口,让外界能够告诉组件内部,需要使用的列的名字,以及每一列都需要从tableData里的那个字段去获取数据。

因此,我们为<TableWithPagination/>提取了一个对外的接口

//在TableWithPagination的props里加上
columns:{
default:[],
type: Array,
},
//在外部调用的时候,传入如下的数据
columns:[
{title:"name-第一列",prop:"name"},
{title:"date-第二列",prop:"date"},
],

然后我们把TableWithPagination里的<template>中添加,如下代码

<el-table
:data="tableData"
stripe
style="width: 100%">
<el-table-column
v-for="item in columns"
:prop="item.prop"
:label="item.title">
</el-table-column>
</el-table>

通过Vue提供的v-for指令,我们把columns里的每一条数据展开为 el-table-column 。这里我们使用的是Element组件来做的演示

PaginationContainer

img

然后我们再来看看<PaginationContainer/>组件,其实对于这个分页容器组件,因为它的展示表格的部分差异很大,所以我们直接把这个差异部分从组件中剔除,留下一对 <solt></solt>标签

<template>
<div>
<slot></slot>
</div>
</template>

外部需要什么样的表格,直接在使用的时候自己定义。

分页标签部分

讲完上面的表格展示部分,我们再来看看底部的分页标签

<el-pagination
class="pagination"
@prev-click="handlePrevClick"
@current-change="handleCurrentChange"
background
:page-size="pageSize"
:pager-count="pagerCount"
layout="prev, pager, next"
:total="total"
>
</el-pagination>

这个分页标签部分,底部的 page-size、page-count、total 都是按照标签需求来写的,没什么特别之处。需要注意的地方是 两个事件,current-changeprev-click我们需要用户点击分页按钮的时候来刷新数据,所以我们分别绑定了handlePrevClickhandleCurrentChange事件

这两个事件对应的实现函数放在methods里,代码如下:

methods: {
handlePrevClick(currentPage){
console.log("handlePrevClick--->",currentPage)
if(this.getData){
this.getData(currentPage)
}
},
handleCurrentChange(currentPage){
console.log("handleCurrentChange--->",currentPage)
if(this.getData){
this.getData(currentPage)
}
}
}

这里的this.getData实际上调用的是外部通过props.getData 属性接口传入到组件内的方法,我们看一下外部调用的函数

//App.vue
data(){
return {
total:10,
tableData: [],
currentPage: 0,
}
},
mounted(){
this.getData( this.currentPage );
},
methods: {
getData(currentPage){
this.currentPage = currentPage;
//准备页面数据
let pageData = [];
for( let i=0; i<5; i++ ){
pageData.push({
date: '2016-05-'+currentPage,
name: '页'+currentPage+"-"+i,
});
}
this.tableData = pageData;
}
},

我们在外部调用的业务代码中,定义了tableData,currentPage之类的数据。然后在组件挂载mounted好之后,首次调用 this.getData( this.currentPage );来获取初始化的内容。然后在后续,用户每次点击底部分页按钮的时候,都会再次调用getData方法,完成页面数据的更新。

这里,我把这两个组件封装涉及到的主要代码进行了讲解。

完整示例代码,点击这里查看

这里给你留下一道课后作业,利用上一期的Server端代码,你能否把本期的例子改造成一个和后台通讯,获取真实API数据的例子

本期内容就到这里,希望对你有所帮助,当然,如果你在学习的过程中遇到问题,欢迎你在我们的【山地人·前端进化岛】VIP群里给我提问,或者在知识星球里给我留言。