前端渣渣開發UI公共組件的新認識

📢 我除了菜,啥都不是前端

前段時間,組裏決定作一個跨項目、跨業務的 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 的實例代碼,使其餘開發者能夠更快上手與他們的使用狀況。

如何設計

  1. 標準性
  2. 獨立性
  3. 複用與易用
  4. 無環依賴原則(ADP)
  5. 入口處檢查參數的有效性,出口處檢查返回的正確性
  6. 穩定抽象原則(SAP)
  7. ......

上邊如何設計我坦白,是從 👉聊聊組件設計 寫過來的,懶得寫了~(尊重做者,尊重原創,你們直接去看他的文章哈~)

着手開發

咱們組裏的組件庫,是基於 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 版本相對較爲寬鬆,先出基礎版,再深挖細節和優化 ~

相關連接

相關文章
相關標籤/搜索