天天看點

元件化設計&實作一個分頁元件

​ 随着前端三大架構(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即可運作項目,檢視實際效果