對於市面上的一些優秀UI庫,如element-ui、Ant Design React、React Material-UI等,其中每個組件的核心實現由兩部分組件:屬性、行爲。而作爲一枚程序員,你是否想過自己去進行一個UI組件庫的實現呢?那麼本篇文章就是從認識組件化思想開始,並逐步爲大家實現一個基礎的UI組件庫。
文章內容大致分爲以下四個部分:
- 重新認識組件
- 數據管理
- 組件的"職責問題"
- UI組件的封裝(button、input、dialog)
重新認識組件
對於大部分前端工程師來說,提到組件這個詞,首先想到的應該是複用(可用性),具體表現就是對數據和方法的簡單封裝,那麼一個組件在實現的過程中,應該從哪些方面去進行思考了,下面簡單列舉了四個點:
- 組件是拿來用的:應該從使用者(程序員)的感受出發
- 沒有"最好怎麼做":需要考慮項目的特點
- 好組件不是設計出來的,是改出來的:經常調整,有時還要重構
- 組件的功能應該單一、簡單:不要試圖把衆多功能塞到一個組件中
劣質的組件庫 | 優秀的組件庫 |
---|---|
混亂的使用風格 | 風格統一 |
不合符用戶預期的使用方式 | 符合直覺的使用方式 |
多餘的步驟 | 直接了當 |
過度的封裝 | 適度的封裝 |
無法定製 | 把代碼寫死 |
數據管理
數據管理應該遵循就近原則:
- 如果數據在兩個組件間公用,則應該在父級中管理此數據
- 不要在全局數據中(redux、vuex)堆放過多數據
情況一:
情況二:
那麼對於需要垮更多層級的組件來說,共享公用(全局數據),適當使用redux、vuex等更靈活、方便
組件的"職責問題"
原則上,組件應該自己搞定自己的工作,而不是讓父級來幫忙(讓子級處理部分工作是可以的,而且是好的),例如一個分頁組件,上一頁、下一頁、第一頁、最後頁等,其細節實現邏輯就應該是該組件本身的職責,而當使用該組件的時候,只是對外提供了“接口”供外界使用,外界根本不關心其實現細節。
這樣的例子在生活中也隨處可見。如電腦的USB接口:
當我們想要使用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