组件化设计&实现一个分页组件

​ 随着前端三大框架(Vue、React、Angular)的发展,相应的也会有element-ui、ant-design等成熟的UI组件库的出现,很大程度上提高了前端开发的效率。如果提供的组件不符合业务要求的时候,需要我们自己动手实现一个合适的组件,那么,为什么要开发组件,什么是组件化思想,实现一个组件的时候我们需要考虑哪些问题?

  • 为什么要开发组件

    • 组件化是对实现的分层,是更有效的代码组合方式(重复使用/一个地方使用)
    • 组件化有利于代码的重组、优化、重构和维护
    • 组件化有利于单元测试
  • 组件化思想

    ​ 简单来说,组件就是将页面中某一部分UI和对应的功能抽离出来,并封装为一个独立的整体,无论放在哪个位置,都可以使用UI和功能,复用性和灵活性较强。组件设计包括以下几个原则:

    • 功能单一,短小精悍
    • 避免太多参数
    • 避免暴露组件内部实现
    • 避免直接操作DOM
    • 入口处检查参数的有效性,出口处检查返回的正确性
    • 引用透明,无副作用
    • 充分分隔变化的部分
  • 怎么实现一个优秀的组件

    实现组件之前我们需要考虑这样几个问题

    • 组件是否还可以拆分,是否还需要拆分,合理划分颗粒度
    • 组件的依赖是够可以缩减
    • 组件是否对其他组件造成侵入(组件封装性不够或自身越界操作)
    • 组件是否可复用于其他类似场景
    • 其他人使用该组件时,是否满足简单上手,接口设计符合大众习惯的条件
    • 业务不需要这个功能时,是否方便清除

    接下来我们以实现一个分页组件为例,来讨论实现一个组件的详细过程

    ​ 分页组件需具备以下功能:合理的动态显示页码;点击某一个页码,响应的会去请求对应数据并展示至页面上;点击前一页,后一页可实现相应的翻页操作;当前页码为第一页,则前一页无法点击,当前页码为最后一页,则无法点击下一页。

    ​ 现在我们开始考虑这样几个问题,组件入口(属性)和组件出口(事件)、可复用、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即可运行项目,查看实际效果

发布了58 篇原创文章 · 获赞 85 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/codezha/article/details/90905325