随着前端三大框架(Vue、React、Angular)的发展,相应的也会有element-ui、ant-design等成熟的UI组件库的出现,很大程度上提高了前端开发的效率。如果提供的组件不符合业务要求的时候,需要我们自己动手实现一个合适的组件,那么,为什么要开发组件,什么是组件化思想,实现一个组件的时候我们需要考虑哪些问题?
为什么要开发组件
组件化思想
简单来说,组件就是将页面中某一部分UI和对应的功能抽离出来,并封装为一个独立的整体,无论放在哪个位置,都可以使用UI和功能,复用性和灵活性较强。组件设计包括以下几个原则:
怎么实现一个优秀的组件
实现组件之前我们需要考虑这样几个问题
接下来我们以实现一个分页组件为例,来讨论实现一个组件的详细过程
分页组件需具备以下功能:合理的动态显示页码;点击某一个页码,响应的会去请求对应数据并展示至页面上;点击前一页,后一页可实现相应的翻页操作;当前页码为第一页,则前一页无法点击,当前页码为最后一页,则无法点击下一页。
现在我们开始考虑这样几个问题,组件入口(属性)和组件出口(事件)、可复用、UI和功能
要实现上图这样的一个分页组件,其实可以进一步拆分,拆分为两个部分,上面的列表展示和下面的分页页码,这样可以满足复用性。
分页页码的实现,需要当前页码(currentPage)、开始页码(startPage)、分组页码(groupCount)、总页数(totalPage)等属性(组件入口);需要页码跳转(pageCallbackFn)事件(组件出口)。
展示列表的实现,需要列表数据(dataList),当点击分页页码执行页码变化操作时,展示列表需要有响应的变化,所以,我们可以将页码跳转方法定义在分页页码的父组件中,这样,我们的组件结构则基本形成,Pagecontainer (分页组件整体)的子组件为Pagecomponent(分页页码)。
分页展示核心思想:当总页数不大于10时,显示全部页码;当总页数大于10时,始终显示首尾页码,当当前页码大于分组页码时,显示省略号。
初始化项目
//安装构建工具,如已安装,可跳过该步骤
npm install -g create-react-app
//初始化项目
create-react-app study_code
初始化项目后,可删除无用代码,添加新的文件和目录,我的项目目录如下图所示
本地json模拟数据
项目目录下新建tsconfig.json
文件,并添加一下代码
[
{"id":1,"name":"hello1"},
{"id":2,"name":"hello2"},
{"id":3,"name":"hello3"},
{"id":4,"name":"hello4"},
{"id":5,"name":"hello5"},
{"id":6,"name":"hello6"},
{"id":7,"name":"hello7"},
{"id":8,"name":"hello8"},
{"id":9,"name":"hello9"},
{"id":10,"name":"hello10"},
{"id":11,"name":"hello11"},
{"id":12,"name":"hello12"},
{"id":13,"name":"hello13"},
{"id":14,"name":"hello14"}
]
Pagecontainer
新建Pagecontainer.js
文件,并添加以下代码
import React, {Component} from 'react'
import Pagecomponent from './pageComponent.js'
import data from '../mock/tsconfig.json'
class Pagecontainer extends Component {
constructor() {
super();
this.state = {
dataList:[],
pageConfig: {
totalPage: data.length //总条数
}
}
this.getCurrentPage = this.getCurrentPage.bind(this)
}
getCurrentPage(currentPage) {
this.setState({
dataList:data[currentPage-1].name
})
}
render() {
return (
<div>
<div style={{padding:'0 300px'}}>
{this.state.dataList}
</div>
<Pagecomponent pageConfig={this.state.pageConfig}
pageCallbackFn={this.getCurrentPage}/>
</div>
)
}
}
export default Pagecontainer
Pagecomponent
新建Pagecomponent.js
文件,并添加以下代码
import React, {Component} from 'react'
import './pageComponent.css'
class Pagecomponent extends Component {
constructor(props) {
super(props)
this.state = {
currentPage: 1, //当前页码
groupCount: 5, //页码分组,显示7个页码,其余用省略号显示
startPage: 1, //分组开始页码
totalPage:1 //总页数
}
this.createPage = this.createPage.bind(this)
}
componentDidMount() {
this.setState({
totalPage: this.props.pageConfig.totalPage
})
this.props.pageCallbackFn(this.state.currentPage)
}
createPage() {
//const {totalPage} = this.props.pageConfig;
const {currentPage, groupCount, startPage,totalPage} = this.state;
let pages = []
//上一页
pages.push(<li className={currentPage === 1 ? "nomore" : null} onClick={this.prePageHandeler.bind(this)}
key={0}>
上一页</li>)
if (totalPage <= 10) {
/*总页码小于等于10时,全部显示出来*/
for (let i = 1; i <= totalPage; i++) {
pages.push(<li key={i} onClick={this.pageClick.bind(this, i)}
className={currentPage === i ? "activePage" : null}>{i}</li>)
}
} else {
/*总页码大于10时,部分显示*/
//第一页
pages.push(<li className={currentPage === 1 ? "activePage" : null} key={1}
onClick={this.pageClick.bind(this, 1)}>1</li>)
let pageLength = 0;
if (groupCount + startPage > totalPage) {
pageLength = totalPage
} else {
pageLength = groupCount + startPage;
}
//前面省略号(当当前页码比分组的页码大时显示省略号)
if (currentPage >= groupCount) {
pages.push(<li className="" key={-1}>···</li>)
}
//非第一页和最后一页显示
for (let i = startPage; i < pageLength; i++) {
if (i <= totalPage - 1 && i > 1) {
pages.push(<li className={currentPage === i ? "activePage" : null} key={i}
onClick={this.pageClick.bind(this, i)}>{i}</li>)
}
}
//后面省略号
if (totalPage - startPage >= groupCount + 1) {
pages.push(<li className="" key={-2}>···</li>)
}
//最后一页
pages.push(<li className={currentPage === totalPage ? "activePage" : null} key={totalPage}
onClick={this.pageClick.bind(this, totalPage)}>{totalPage}</li>)
}
//下一页
pages.push(<li className={currentPage === totalPage ? "nomore" : null}
onClick={this.nextPageHandeler.bind(this)}
key={totalPage + 1}>下一页</li>)
return pages;
}
//页码点击
pageClick(currentPage) {
const {groupCount} = this.state
const getCurrentPage = this.props.pageCallbackFn;
//当 当前页码 大于 分组的页码 时,使 当前页 前面 显示 两个页码
if (currentPage >= groupCount) {
this.setState({
startPage: currentPage - 2,
})
}
if (currentPage < groupCount) {
this.setState({
startPage: 1,
})
}
//第一页时重新设置分组的起始页
if (currentPage === 1) {
this.setState({
startPage: 1,
})
}
this.setState({
currentPage
})
//将当前页码返回父组件
getCurrentPage(currentPage)
}
//上一页事件
prePageHandeler() {
let {currentPage} = this.state
if (--currentPage === 0) {
return false;
}
this.pageClick(currentPage)
}
//下一页事件
nextPageHandeler() {
let {currentPage,totalPage} = this.state
if (++currentPage > totalPage) {
return false;
}
this.pageClick(currentPage);
}
//根据输入框有效值跳转相应页码
jumpTo(e){
let val = parseInt(e.target.value);
if(!val){
return false;
}
this.pageClick(val);
e.target.value = '';
}
render() {
const pageList = this.createPage();
return (
<ul className="page-container">
{pageList}
<span>总共{this.props.pageConfig.totalPage}条,共{this.state.totalPage}页,到第</span>
<input className="jumpTo" type="text" onBlur={this.jumpTo.bind(this)}/>
<span>页</span>
</ul>
)
}
}
export default Pagecomponent
新建Pagecomponent.css
文件,并添加以下代码
.page-container:after {
content: '.';
display: block;
height: 0;
overflow: hidden;
clear: both;
}
.page-container {
zoom: 1;
line-height:30px;
}
.page-container li {
list-style: none;
float: left;
padding: 0px 8px;
cursor: pointer;
border: 1px solid #ccc;
height: 28px;
line-height: 28px;
margin-right: 8px;
}
.page-container li:last-child {
margin-right: 0px;
}
/*当前页样式*/
.activePage {
color: #fff !important;
background: #2292bd;
border-color: #2292bd !important;
}
/*没有上一页和下一页时样式*/
.nomore {
color: #b5b5b5 !important;
cursor: not-allowed !important;
}
.jumpTo{
width:30px;
}
APP.js中引入Pagecontainer 组件
import React, { Component } from 'react';
import './App.css';
import PageContainer from './pagination/pageContainer.js'
class App extends Component {
render() {
return <PageContainer></PageContainer>
}
}
export default App;
执行npm start即可运行项目,查看实际效果