「技術分享」以Antd為例,快速打通UI組件開發的任督二脈

語言: CN / TW / HK

theme: condensed-night-purple highlight: atelier-heath-light


前言

猶記得,我還是一個初入職場的新人,出去面試總會被問到會不會組件開發的問題。當時項目開發都使用現成的UI組件,最初用Element UI,後來換成了Antd。無論換哪種組件,都幫助節省了大量的開發時間,自己平時組件開發,最多就進行一些簡單的標題、彈窗、表格的二次封裝。總之就是,組件開發的“道行”尚淺,所以面試的時候底氣略微不足。

經過歲月的沉澱,經驗的累積,自己開發一套UI組件已經不是什麼困難事的時候。打開Antd的源碼,想研究一下,Antd的技術團隊,是怎麼實現我們在官網看到的這些組件的。

講一個我之前年少無知的往事。最開始使用Element,還挺困惑的。就琢磨着餓了麼不是送外賣的嘛,怎麼還提供上技術的組件庫了?後來才知道,人家的技術團隊也非常非常的厲害。

「寫過通用組件嗎?」

這道面試題的關鍵在於,通用組件怎麼寫。

系統特性

現今,UI組件庫豐富且成熟,所以可能覺得日常開發中,通用組件會寫的很少,其實不然。

每個系統,無論是業務特性、交互特性還是UI特性,都可以整理出一部分通用組件,比如標題、頁面佈局、列表、可編輯表格、模糊搜索框等

以列表為例

Antd有現成的Table組件,但是我們實際開發中,一般列表管理頁是帶搜索項以及數據展示的,有可能還帶搜索重置按鈕或者搜索導出按鈕。

所以通用組件就有用武之地了,一次封裝,千千萬萬的列表管理頁面就都可以用一個組件搞定了。

```js {!resetAble && ( )} {exportable && ( )}

; ``` ## 功能通用 已知日常開發中的部分功能確實可以做成通用組件,那麼怎麼界定通用的邊界呢? 通用性過高會導致代碼過於複雜,通用性過低,開發效率會變低。我一般會觀察以下兩點: 1. 用到這個功能的時候,和業務可能關係不大,UI或者交互操作,在任何業務線下都需要這樣設計,比如可編輯表格。 2. 使用頻率,這個要加一點對未來業務發展的預判。比如搜索項中的省份和城市,需要實現模糊搜索匹配的功能。 未來無論怎麼樣的業務,只要有省份、城市這兩項基本都需要這個功能。 ```js ; ``` ## 參數設計 通用組件,差異的部分,一般在功能設計的時候會通過外部傳參區分或者控制。所以開發通用組件,參數設計是重要的一個環節。 如果剛開始不是很擅長設計參數,可以參考Antd的參數設計,Antd的組件豐富且功能強大,所以參數考慮的也很周全。邊學邊練,效果更佳。 如圖為Antd的Input輸入框組件「平平無奇」的參數: ![](http://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b227e748347b4c2a8b883024b697103f~tplv-k3u1fbpfcp-zoom-1.image) # Antd組件功能賞析 電影有精彩片段賞析,Antd的組件很豐富,如果一一列舉,詳細介紹,可能我要寫到下個月,所以我選了幾個常見且基礎的組件,來看看Antd是怎麼設計這些組件的。 官網指路☞[Ant Design](http://ant.design/components/overview-cn/) ## 賞析前準備 學習第三方組件之前,不能盲目看代碼,可能會找不到重點或者被大量的邏輯繞暈。我一般學習之前先做三方面準備: - 先明確組件要實現什麼功能,比如輸入框是否不可操作,是否回顯數據等; - 然後看組件參數,把參數分為控制UI佈局、控制內容展示、控制操作功能等幾種;比如通過disabled的值控制輸入框是否可以操作,通過設置value的值進行數據回顯等; - 最後去思考這些參數怎麼實現具體的功能,就比較容易想清楚了。 ## Grid 柵格 柵格化佈局,基於行(row)和列(col)來定義信息區塊,可以將區域24等分。通過 row 在水平方向建立一組 column,內容放置於 col 內。 ### 展示層 看col文件中這三行代碼,和各種style、className變量。不難發現,柵格化佈局主要是通過組件參數對樣式的控制來實現的。 ```js return (
{children}
); ``` ### 佈局設計 結合參數説明和代碼分析,可以大致總結出柵格佈局的設計如下: 1.柵格組件基於 Flex 佈局。 2.柵格的佔位格數,也是它的寬度,樣式實現時使用百分比,比如span的值為6時,24等分之後,它的百分比是25%。 ``` .ant-col-6 { display: block; flex: 0 0 25%; max-width: 25%; } ``` 3.區塊間隔格數的值實際上是設置的padding值的2倍,是相鄰兩個模塊的間距之和。所以代碼中進行了除以2的處理。 ``` if (gutter && gutter[0] > 0) { const horizontalGutter = gutter[0] / 2; mergedStyle.paddingLeft = horizontalGutter; mergedStyle.paddingRight = horizontalGutter; } ``` 4.響應式佈局,支持六個響應尺寸:xs、sm、md、lg、xl、xxl。參數支持多類型可以是number類型,也可以是Object類型。使用typeof判斷參數類型。 ``` if (typeof propSize === 'number') { sizeProps.span = propSize; } else if (typeof propSize === 'object') { sizeProps = propSize || {}; } ``` 佈局功能分析告一段落,柵格組件賞析也就收工了。 ## Steps 步驟條 我們來看看步驟條的功能。 - 步驟條狀態,已完成、進行中、未開始、運行錯誤。 - 兩種展示方式,橫向和縱向。 - 不同展示類型,數值類、自定圖標類、點狀類。 - 內容展示,標題、子標題、詳情描述。 ### rc-steps 我在看Antd的源碼時發現,有些組件底層用的第三方[react-component](http://github.com/react-component)中的組件。當然這個組件庫也是屬於Antd的。所以想研究Steps組件的功能,需要翻另一個組件庫的代碼[react-componentr/steps](http://github.com/react-component/steps)。 ```js import RcSteps from 'rc-steps'; ``` ### 步驟條狀態 既可以通過status直接指定當前步驟狀態,也可以通過對比current和步驟的數值確定步驟的狀態。 ```js const stepNumber = initial + index; if (status === 'error' && index === current - 1) { childProps.className = `${prefixCls}-next-error`; } if (!child.props.status) { if (stepNumber === current) { childProps.status = status; } else if (stepNumber < current) { childProps.status = 'finish'; } else { childProps.status = 'wait'; } } ``` ### 展示類型 步驟條支持多種不同的展示類型,代碼實現上主要是通過條件語句判斷。 - 點狀類型,支持自定義展示。當點狀步驟條參數progressDot的值是函數類型時,會使用傳入的值;否則使用內部定義的點狀展示內容。 - 自定義圖標,參數icon表示步驟圖標的類型,當它有值的時候,步驟條會顯示成它的值。有兩個特殊的圖標:成功狀態、失敗狀態,這兩個狀態的圖標如果使用組件時沒有進行自定義,會取內部定義的圖標。 - 默認類型,放到條件判斷最底層,當其他判斷條件的參數沒有值時,步驟條會展示內部定義的默認類型。 **條件判斷** ![](http://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/17d16ca3f30b47e29c6a8a0e5c68661a~tplv-k3u1fbpfcp-zoom-1.image) **內部定義的成功和失敗的圖標** ```js const icons = { finish: , error: , }; ``` ## Table 表格 Antd的Table表格,功能很強大,單看文檔中的使用介紹就能感覺出來,可用功能大概30多種。我帶着這些功能是怎樣實現的好奇心,研究了Antd的源碼。內容有點多,我挑基礎的部分講一講。 ![](http://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ffbd4527953f44c2a74dd59760f64788~tplv-k3u1fbpfcp-zoom-1.image) ### rc-table Table組件,底層主要使用[react-component](http://github.com/react-component)中的[table](http://github.com/react-component/table)組件。 ### columns - 參數columns表示表格列的配置描述,表格有哪些列表項都是通過它定義的。 - Tabel組件會將columns傳入RcTable組件。 - columns的值確定表頭thead都有哪些分組。 - tbody中表格項的值,也是通過columns中列表項的dataIndex變量,從參數dataSource中找到對應的值。 ```js {flattenColumns.map((column: ColumnType, colIndex) => { const { render, dataIndex, className: columnClassName } = column; return ( ); })} ``` ### dataSource - Table的參數dataSource實現表格數據回顯。 - dataSource傳入Tabel組件會根據分頁功能處理成pageData對象,傳入RcTable組件。 - 在RcTable組件中,表格列展示內容是封裝到子組件Body中的。組件Body會先循環渲染表格的行數據,每一行下面包含一個BodyRow子組件 - BodyRow子組件,行數據會進行循環單元格數據,而單元格的內容封裝在Cell子組件中。 - Cell單元格組件中,結合columns中的dataIndex確定最終回顯的值。 其中單元格的標籤會根據傳入的component的值不同,使用不同的標籤,默認為td,表頭thead傳入的為tr。 ```js component: Component = 'td', ...... return ( {appendNode} {mergedChildNode} ); ``` Table組件比較複雜,功能比較豐富,組件的顆粒度也很細,我研究columns和dataSource就花了不少時間,更多的功能,後面再慢慢探索吧。 # 總結 多看一些優秀的項目源碼,可以幫助拓展開發思路,提升技術設計思維。 現在有Antd等優秀的UI組件庫,好像是不用重複造輪子了。但是奔着學習的目的,去開發一套UI組件還是可以幫助提升技術的。當然這些都是給初級開發者的建議,大佬們,大佬們的技術能力,我還在努力追趕。 組件系列的分享告一段落,後面想換換思維,學習一下游戲開發。下個系列——「記憶力的小遊戲」見咯~ 我正在參與掘金技術社區創作者簽約計劃招募活動,[點擊鏈接報名投稿](http://juejin.cn/post/7112770927082864653)。 > **輕鬆一笑** > > 聚餐,來兩盤土豆絲,為什麼是土豆絲,因為便宜;為什麼兩盤,因為一盤不夠。