📢 我除了菜,啥都不是前端
前段時間,組裏決定作一個跨項目、跨業務的 UI 組件庫,緣由是咱們部門的產品愈來愈多,且每一個產品設計到多端(如 Web/Mobile/PC/Android 等)而爲了快速響應目標,決定作一套統一且可視化的,擁有部門特點的 UI 組件庫。git
視覺已經給出了全部組件樣式、交互效果,而咱們前端組內部也通過一輪輪的評審和討論,最終每一個人都分了幾個組件進行開發。而我呢,也分到了幾個組件,有稍微簡單點的,有存在複雜交互及狀態的,這篇文章,主要是記錄本身第一次開發一個公共組件的思考~github
我負責的組件是 : Skeleton 骨架佔位組件 、Card 卡片組件、Button 按鈕組件、柵格組件npm
這屬於第一次開發公共組件,以前呢主要都仍是在項目裏邊,抽離一些簡單複用邏輯的業務組件,舉個例子,對於 Button
組件,與我來講,我以前更多可能考慮的就只是一些經常使用的狀態,好比說以前的Button
組件代碼是這樣的 :json
/** * @class Button * @extends {React.Component} * @property {string} text - 按鈕文本 * @property {string} size - 按鈕大小,small/middle/big * @property {string} icon - 按鈕攜帶的icon,不須要則爲空 * @property {string} color - 類型,可選值爲 orange/ghost/white * @property {object} style - 樣式 * @property {string} textSize - 按鈕文案文字大小,small/middle/big/super * @property {boolean} disabled - 能否點擊 * @property {string} iconSeat - 按鈕icon的位置,left/right * @property {function} onHandleClick - 點擊事件 * @property {boolean} isLock - 是否鎖定點擊(注:若是須要使用鎖,請保證全部操做爲同步或者全部的異步行爲執行完再return) */
declare interface ButtonProps {
text?: string
size?: string
icon?: string
color?: string
style?: object
textSize?: string
disabled?: boolean
iconSeat?: string
onHandleClick?: () => void
isLock?: boolean
}
複製代碼
這是我結合業務內容,抽離的Button
組件,有一絲絲公共組件的樣子,可是其實仍是遠遠不夠的。因而,這次在開發公共組件以前,我特地的去作了充足的準備。設計模式
什麼是組件化?這個問題相信大部分前端工程師都知道~ 在跟我小學弟學妹們裝 X 的時候,他們問我,什麼是組件,我笑而不語,甩出了www.baidu.com
網址,告訴他們,本身查...數組
組件化是指解耦複雜系統時將多個功能模塊拆分、重組的過程,有多種屬性、狀態反映其內部特性。前端工程師
簡單來講,咱們能夠把頁面看成是變形金剛,由各不一樣零件組件,好比說 Header零件
、Hand零件
、Footer零件
等...antd
而後呢,在咱們想要製造一個變形金剛時,直接就用這些零件,就能快速 do 出一個產品了~app
相信你們也不想聽我逼逼,直接進入主題,我想要設計一個你們都能廣泛使用的組件,我該如何去設計,我上網搜了許多相關的文章,例如 :
在我看了一些文章以後,整理了一些其它人對組件設計的見解(底部會貼出友情連接),首先,咱們得擁有一套組件化設計思惟,要它有啥用?它能幫咱們高效開發啊~
這個文檔必須詳細,否則別人咋看,同時每個組件,都應儘量的表達,該組件的由來、使用場景、如何設計、API、傳參等
👉 感興趣的能夠去看看 ant design 的文檔,它除了使用文檔,在 github 上還有每一個組件的說明文檔
應該提供一個可讓開發者實時調試代碼的地方,使其餘這些組件的使用者能夠更好地理解各個 props,相信比較流行熱門的 UI 庫,都有這種騷操做~
提供一些如何將其數據導入 UI 的實例代碼,使其餘開發者能夠更快上手與他們的使用狀況。
上邊如何設計
我坦白,是從 👉聊聊組件設計 寫過來的,懶得寫了~(尊重做者,尊重原創,你們直接去看他的文章哈~)
咱們組裏的組件庫,是基於 Ant Design
進行開發,嗯,我一開始覺得是項目中已經 npm install antd
了,誰知道當我去看 package.json
時,發現並沒得,因而我去問了一下負責這個組件庫的 C 同窗,原來...是要咱們去看 Ant Design 的代碼, 而後借鑑一波,去除國際化、還有一些不一樣的差別項,再加入本身部門特點的交互、樣式~
奧力給,這啥啊,什麼玩意啊,就直接去看源碼了 ???
因而,我挑選了一個最簡單的 Card 組件,進行研究了一波,wc,不看不知道,一看嚇一跳...原來我仍是太菜了...
這個卡片組件,若是沒看源碼以前,我估計就是這樣 :
/** * @class Card * @extends {React.Component} * @property {string} title - 標題 * @property {string} content - 內容 * @property {string} size - 卡片大小 * @property {string} extra - 右上角extra * @property {object} style - 樣式 * @property {function} onClick - 點擊事件 */
複製代碼
沿着這個思路,一路走下去,發現,若是傳 content
確定不對啊,爲啥,若是用戶想改這個文案內容的樣式呢,簡單,給他開放一個 contentStyle
就行了嘛~
那若是用戶想換行,又該咋辦,簡單,這個 content<String>
就改爲 content<Array>
嘛,判斷 typeof content
,若是是數組就遍歷渲染文案~
那若是用戶傳ReactNode
類型的呢,好比這樣
const loadingNode = (
<div>loading</div>
)
<Card content={loadingNode} />
複製代碼
再或者,用戶想這樣~
const loadingNode = `<p className="loading">我是loading</p>`;
<Card content={loadingNode} />; 複製代碼
用戶真實想要的是,你經過 dangerouslySetInnerHTML
進行轉義,而不是你直接顯示這個 content。
算了不想了,直接去看源碼吧 ~
初次一看,啥啊,這個 config-provider
是個啥玩意?這個 SizeContext
又是個啥,這個 less 文件咋都是用的 @xxx
啊,怎麼一個文件引入的這麼多變量,都是外部的。
可是當我去看完一個完整組件以後,才發現,Ant Design 🐂 B !!!
👉 若是你感興趣的戳這裏看源碼: Ant Design-Card
以我本身開發的 Card
組件來舉例~
咱們來看看,我這個 Card 組件所具有的功能 ...
參數 | 說明 | 類型 | 默認值 | 是否必須 | 版本 |
---|---|---|---|---|---|
size | 卡片大小 | string | middle | 是 | - |
style | 卡片樣式 | object | - | 否 | - |
loading | 當卡片內容還在加載中時,能夠用 loading 展現一個佔位 | boolean | true | 否 | - |
isShadow | 卡片是否存在陰影 | boolean | true | 否 | - |
title | 卡片標題 | string|ReactNode | - | 否 | - |
headStyle | 自定義標題區域樣式 | object | - | 否 | - |
headWrapName | 自定義標題區域 className | string | ${prefixCls} -card-head |
否 | - |
extra | 卡片右上角的操做區域 | string|ReactNode | - | 否 | - |
onClick | 卡片點擊事 | () => void | - | 否 | - |
因此天然而然的,咱們的 Props 就是這些玩意了~
export interface AbstractCardProps {
size?: string;
style?: React.CSSProperties;
loading?: Boolean;
isShadow?: Boolean;
title?: string | React.ReactNode;
headStyle?: React.CSSProperties;
headWrapName?: string;
extra?: string | React.ReactNode;
onClick?: () => void;
}
複製代碼
而後呢,咱們經過引入 import { ConfigConsumer, ConfigConsumerProps } from '../config-provider'
進行處理,別問,問就是還沒咋搞懂,總之就是高階組件的瘋狂操做,感興趣的能夠去看看,這個玩意真的有點意思 👉 config-provider
咱們接着對一些 props 進行處理 ~
// 獲取前綴,好比像ant-design同樣,全部的class都是以 antd- 開頭
const prefixCls = getPrefixCls('card', customizePrefixCls);
// 定義頭部
let head: React.ReactNode;
// 定義加載時的狀態
let loadingBlock: React.ReactNode;
if (title || extra) {
head = (
<div style={headStyle} className={`${prefixCls}-head ${headWrapName && headWrapName}`} > <div className={`${prefixCls}-head-wrapper`}> {title && <div className={`${prefixCls}-head-title`}>{title}</div>} {extra && <div className={`${prefixCls}-extra`}>{extra}</div>} </div> </div>
);
}
const body = (
<div className={`${prefixCls}-body`}>{loading ? loadingBlock : children}</div>
);
複製代碼
在咱們導出以前,咱們經過SizeContext
高階組件進行包裝~
<SizeContext.Consumer>
{size => {
// 若是你有自定義的size,以你的爲準,沒有則以SizeContext中默認的size
const mergedSize = customizeSize || size;
// 處理全部的className
const classString = classNames(prefixCls, className, {
[`${prefixCls}-loading`]: loading,
[`${prefixCls}-shadow`]: isShadow,
[`${prefixCls}-${mergedSize}`]: mergedSize
});
return (
<div className={classString} style={style} onClick={onClick && onClick}>
{head}
{body}
</div>
);
}}
</SizeContext.Consumer>
複製代碼
對於樣式,不是直接在 less
文件寫一些 color 或者 font-size 的,對於ant-design
來講,他們有一個 style 文件,裏邊存放着許多定義好的變量,甚至於 theme 文件,能夠說,到時候若是想自定義主題,只須要講其中的 theme 文件 copy 一份,而後進行修改,就能直接完成自定義主題的需求了~
怎麼說呢,其實抽離了一些複雜的需求出去以後,相對的這個 Card 組件就簡單了不少,咱們再多去看看幾個組件,會發現,真香,原來組件還能夠這麼設計,相對本身以前設計的那些low B組件,這個組件看起來就高大上太多了。🙃
回過頭來看,這篇文章有點像隨手寫的筆記,沒得啥乾貨,不過主要的目的仍是想傳遞給你們一個思想:就是有時間,能夠考慮去看看一些優秀組件的源碼 ~ 奧力給 !
組內目前的一個進度也在有序進行中,畢竟你們都一致認同這個項目,且 v1 版本相對較爲寬鬆,先出基礎版,再深挖細節和優化 ~