深入理解組件化思想及手動封裝實現一個React UI庫

對於市面上的一些優秀UI庫,如element-ui、Ant Design React、React Material-UI等,其中每個組件的核心實現由兩部分組件:屬性、行爲。而作爲一枚程序員,你是否想過自己去進行一個UI組件庫的實現呢?那麼本篇文章就是從認識組件化思想開始,並逐步爲大家實現一個基礎的UI組件庫。

文章內容大致分爲以下四個部分:

  1. 重新認識組件
  2. 數據管理
  3. 組件的"職責問題"
  4. UI組件的封裝(button、input、dialog)

重新認識組件

對於大部分前端工程師來說,提到組件這個詞,首先想到的應該是複用(可用性),具體表現就是對數據和方法的簡單封裝,那麼一個組件在實現的過程中,應該從哪些方面去進行思考了,下面簡單列舉了四個點:

  1. 組件是拿來用的:應該從使用者(程序員)的感受出發
  2. 沒有"最好怎麼做":需要考慮項目的特點
  3. 好組件不是設計出來的,是改出來的:經常調整,有時還要重構
  4. 組件的功能應該單一、簡單:不要試圖把衆多功能塞到一個組件中
劣質的組件庫 優秀的組件庫
混亂的使用風格 風格統一
不合符用戶預期的使用方式 符合直覺的使用方式
多餘的步驟 直接了當
過度的封裝 適度的封裝
無法定製 把代碼寫死

數據管理

數據管理應該遵循就近原則:

  • 如果數據在兩個組件間公用,則應該在父級中管理此數據
  • 不要在全局數據中(redux、vuex)堆放過多數據

情況一:

情況1
情況二:
那麼對於需要垮更多層級的組件來說,共享公用(全局數據),適當使用redux、vuex等更靈活、方便

組件的"職責問題"

原則上,組件應該自己搞定自己的工作,而不是讓父級來幫忙(讓子級處理部分工作是可以的,而且是好的),例如一個分頁組件,上一頁、下一頁、第一頁、最後頁等,其細節實現邏輯就應該是該組件本身的職責,而當使用該組件的時候,只是對外提供了“接口”供外界使用,外界根本不關心其實現細節

這樣的例子在生活中也隨處可見。如電腦的USB接口:

Usb接口
U盤

當我們想要使用USB接口進行數據傳輸(通過U盤),我們只需要知道我們的U盤適合插2.0接口還是3.0接口,這樣就可以進行數據傳輸了,並不需要知道電腦上提供的usb2.0、3.0接口的具體實現過程以及細節。

UI組件的封裝

組件的繼承

通過把單一組件組合起來完成更復雜的功能,接下來會對button、input、dialog等組件進行封裝,來實現不同場景的dialog,如註冊、刪除等的dialog。該例子會以bootstrap庫爲基礎(比較懶,不想寫css)

Button組件的封裝:

首先來想一個問題,一個按鈕有哪些屬性、哪些方法?最基本的屬性應該有id或class(描述寬高、背景、邊框等)、可用、禁用、childen等,方法有點擊(click)。那麼在遵循React對組件的寫法上以及所擁有屬性和方法我們來進行封裝操作。

先來看一下原始代碼:

<button type="button" class="btn btn-primary">按鈕</button>

以及效果圖:

圖片描述

封裝後的組件,HCButton.js:


import React, { Component } from 'react';

class HCButton extends Component {
    render() {
        return (
            <div>
                <button 
                    type="button" 
                    className={['btn',`btn-${this.props.type||'default'}`,this.props.className].join(' ')}
                    onClick = {this.props.onClick}
                    disabled = {this.props.disabled}
                >
                    {this.props.children}    
                </button>
            </div>
        );
    }
}

export default HCButton;

從上面封裝的代碼,來進行props對象不同屬性的分析:

  • type、className:來控制按鈕外在的顯示情況
  • onClick:來控制按鈕的點擊事件
  • disabled:控制按鈕是否可用
  • children:控制按鈕上顯示不同文字內容

使用說明

屬性 類型 必傳 描述
type string 值有:primary、success、warning、danger等
className string 爲按鈕增加自定義類
onClick function 按鈕點擊事件
disabled boolean 控制按鈕是否可用
children string 按鈕上顯示的內容

使用情況一
APP.js

import HCButton from './components/HCButton'
class App extends Component {
     fn(){
        alert('按鈕點擊了')
     }
     render() {
        return (
          <div>
             <HCButton 
                type="success"
                onClick={this.fn.bind(this)}
                >
               確定
            </HCButton>'
         </div>   
}

運行結果一

圖片描述

使用情況二

    <HCButton 
        type="danger"
        onClick={this.fn.bind(this)}
        className="big-btn"
        disabled
        >
       刪除
    </HCButton>


    .big-btn{
      width: 200px;
      height: 150px;
      margin-left: 200px;
      margin-top: 200px;
    }
    

運行結果二

圖片描述

Input組件的封裝:

封裝思路也是類似Button組件的封裝,一個input標籤大致有的屬性:type、id、class、placeholder、name等,方法大致有:oninput、onfocus、onblur、onchange等。

原始代碼:

<input type="text" class="form-control" id="username" name="username" placeholder="請輸入用戶名">

及效果圖:

圖片描述

封裝後的組件,HCInput.js:

import React, { Component } from 'react';

class HCInput extends Component {
    constructor(props){
        super(props);
        this.value = this.props.value || ''
    }
    handleInput(event){
        this.value = event.target.value;
        this.props.onInput && this.props.onInput(event.target.value);
    }
    render() {
        return (
            <div>
                <input 
                    type={this.props.type || 'text'} 
                    className={['form-control',this.props.className].join(' ')} 
                    id={this.props.id} 
                    name={this.props.name} 
                    placeholder={this.props.placeholder}
                    onInput={this.handleInput.bind(this)}
                    defaultValue={this.value}
                >

                </input>
            </div>
        );
    }
}

export default HCInput;

從上面封裝的代碼,來進行props對象不同屬性的分析:

  • type:輸入框的類型
  • className:輸入框自定義類
  • id:輸入框的id
  • name:輸入框的name
  • placeholder:輸入框的placeholder
  • onInput:輸入框內容改變的事件
  • defaultValue:輸入框默認顯示的內容

使用說明

屬性 類型 必傳 描述
type string 值有:text、password、email等
className string 爲輸入框自定義類
id string 輸入框的id
name string 輸入框的name
placeholder string 輸入框提示信息
defaultValue string 輸入框默認值
onInput funtion 通過該事件實時獲取輸入框的內容

使用情況一

fn1(val){
    console.log('val:',val)
}
render() {
    return (
      <div>
          <HCInput 
              type="text"
              name="username"
              placeholder="請輸入用戶名"
              onInput = {this.fn1.bind(this)}
            />
       </div>
     )
     
}     

運行結果一

圖片描述

使用情況二

圖片描述

Dialog組件的封裝
思想和前面封裝Button、Input類似,就不再贅述了。

原始代碼:

<!--dialog-->
    <div class="dialog-shadow"></div>
    <div class="panel panel-default dialog-panel">
      <div class="panel-heading">
        <h2 class="panel-title">
          登錄
          <a href="#" class="glyphicon glyphicon-remove pull-right"></a>
        </h2>
      </div>
      <div class="panel-body">
        <!--內容-->
        內容放在這裏
      </div>
    </div>

及運行效果圖:

圖片描述

封裝後的組件,HCDialog.js:

import React, { Component } from 'react';

class HCDialog extends Component {
    constructor(props){
        super(props);
        this.state = {
            show:false
        }
    }
    open(){
        this.setState({
            show:true
        })
    }
    close(){
        this.setState({
            show:false
        })
    }
    render() {
        return (
            <div>
                {
                    this.state.show ? (
                        <div>
                            {
                                this.props.shadow===false ? (<div></div>):(
                                    <div className="dialog-shadow"></div>
                                )
                            }
                            <div className="panel panel-default dialog-panel">
                            <div className="panel-heading">
                                <h2 className="panel-title">
                                {this.props.title || '對話框'}
                                {
                                    this.props.closeBtn === false ? (<div></div>):(
                                        <a className="glyphicon glyphicon-remove pull-right" onClick={this.close.bind(this)}></a>
                                    )
                                }
                                
                                </h2>
                            </div>
                            <div className="panel-body">
                                {this.props.children}
                            </div>
                            </div>
                        </div>    
                    ):(<div></div>)
                }
            </div>
        );
    }
}

export default HCDialog;

從上面封裝的代碼,來進行props對象不同屬性的分析:

  • shadow:控制彈窗遮罩層是否顯示
  • title:對話框title
  • closeBtn:控制對話框關閉按鈕是否顯示
  • children:對話框真正顯示的內容

使用說明

屬性 類型 必傳 描述
shadow boolean 控制彈窗遮罩層是否顯示
title string 對話框title
closeBtn boolean 控制對話框關閉按鈕是否顯示
children any 對話框真正顯示的內容

使用情況一,登錄框

    <HCDialog 
      ref="dialog" 
      title="登錄"
      shadow={true}
      closeBtn={true}
    >
     <div className="u-login">
      <HCInput 
        type="text"
        name="username"
        placeholder="請輸入用戶名"
        onInput = {this.handleGetUName.bind(this)}
      />
      <HCInput 
        type="password"
        name="password"
        placeholder="請輸入密碼"
        ref="pwd"
        onInput = {this.handleGetPwd.bind(this)}
      />
      <HCInput 
        type="email"
        name="email"
        placeholder="請輸入郵箱地址"
        value="[email protected]"
      />
     </div>
     <div className="login-btns">
       <HCButton type="primary" className="btn-login-ok">登錄</HCButton>        
       <HCButton  className="btn-login-cancle">取消</HCButton>
     </div>
    </HCDialog>

運行效果圖一:

圖片描述

使用情況二,刪除框:

 <HCDialog 
      ref="dialog" 
      title="刪除"
      shadow={false}
      closeBtn={true}
    >
     該條商品信息刪除後不再恢復,是否刪除?
     <div className="login-btns">
       <HCButton type="danger" className="btn-login-ok">刪除</HCButton>
       <HCButton  className="btn-login-cancle">取消</HCButton>
     </div>
 </HCDialog>

運行效果圖二

圖片描述

OK,以上就是關於基於組件封裝思想以及簡單實現幾個例子的封裝。最後祝大家中秋快樂,O(∩_∩)O

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章