随着前端三大架構(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 元件
執行npm start即可運作項目,檢視實際效果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;