這是一篇不知道題目該是啥的竟品調研文章。最近調研了幾個國外優秀的開源元件庫,一個是為了之後可能做基礎元件庫做技術儲備,另一個是為了跳出國內 antd 的“舒適圈”,看看對於元件庫來說國外的團隊是如何設計的。
寫在前面的總結
為了便於大家直接看結論,這裡我把本來在內網寫在最後的結論挪到了開頭。(因為從內網看大家其實不太想看我怎麼分析原始碼的,只想看結論以及我會怎麼做...)
架構實現
從程式碼架構來說,大致分為以下幾類:
- 單包多元件:以 antd 為代表,所有元件統一版本;
- 多包多元件:國外元件庫大部分採用這種架構,每個元件都想有單獨的版本,大版本保持一致;
元件顆粒度
從元件顆粒度來說,大致分為以下幾類:
- 粗顆粒度:以 antd Modal 為例,這類元件的自定義渲染插槽以 render props 形式提供;
- 細顆粒度:以 Radix-ui 大部分元件為例,這類元件以容器 + children 子元件的形式提供,主張子元件的自由組合,靈活度高;
- 更細的顆粒度:以 React-Spectrum 元件為例,這類元件不僅在 UI 層以細顆粒度的形式提供,還在邏輯成拆分出更細顆粒度的邏輯 hooks;
樣式系統設計
從樣式系統設計來說,大致分為以下幾類:
- 無樣式系統:以 rc 、headless ui 為例,這類元件庫不提供任何樣式檔案,全由業務方外部實現;
有自己的設計語言:
- 樣式檔案:以 antd 為例,這類元件庫的樣式以外部樣式檔案的形式提供給元件,使用方可外部通過 className、style 覆蓋樣式;
- StyledComponents:以 Chakra-ui 為例,這類元件庫的樣式以 css in js 的形式內建在元件內,使用方可通過自定義主題、className 覆蓋樣式;
我的看法
其實當我看完 Charkra-ui 的實現後內心已經有了做基礎元件庫大概的設計。
首先從樣式系統來講,我們重新做元件庫的一個目的就是為了避免樣式汙染,那麼假設設計語言已經達成統一的前提下我們是不是可以激進一點的採用 css in js 的形式去編寫我們的元件?
接下來從元件的結構來說,做過業務元件的同學可能都知道,業務元件簡單來講就是針對某個場景組合基礎元件,而組合的過程中往往只用到基礎元件的某一或幾個功能(甚至不滿足的時候還得很噁心的閹割重寫),那麼再設計基礎元件的時候,是不是可以將本就原子化的基礎元件更加原子化的設計?(例如現在國外的那些基礎元件)
那麼挑戰就來了:
- 是不是更加原子化的設計就意味著使用方需要寫更多的組合邏輯在業務程式碼裡?
- 採用不相容 antd api 的設計是不是意味著存量業務的使用方吃屎般的相容遷移成本?
- ...
不過我倒是想出了一種解決方案,針對上面一些問題,我們是不是可以設計一個適配層來磨平新元件和 antd 之間的 api 差異,適配層根據 antd 的 api 封裝一層“皮”來去給存量業務去使用。
那麼按照這樣子設計,我們的元件庫架構是不是就是這個樣子:
首先對於業務元件庫,底層全部使用新的基礎元件重構,對外不改變原有 api。而對於業務元件庫的使用方來說,無感知。而對於存量業務,鎖死指定版本的 antd api,並提供相容 antd api 的適配層來平穩過渡,對於增量業務,建議直接使用基礎元件庫開發。(當然使用適配層我也不知道...)
(以上只代表我個人的觀點...)
寫在後面的元件庫一覽
Ant-Design
- 倉庫:https://github.com/ant-design...
- 官網:https://ant.design/index-cn
架構模式:UI 實現(antd)+ headless 實現(rc)
- UI 實現:單倉庫、單包、多元件,每個元件基於 headless 元件做 UI 和事件封裝,按指定版本引入 headless 元件;
- headless 實現:多倉庫、單包、單元件,MutiRepo,無樣式,僅實現 dom 和邏輯;
antd 最大的特點就是 UI 和 dom + 邏輯分離,對於元件內自定義渲染的場景,多以元件 + render props 的形式存在,以 Modal 為例:
在樣式系統上,antd 帶給我們的是最常見的一個元件配合一個樣式 less 檔案。
Chakra-ui
- 倉庫:https://github.com/chakra-ui/...
- 官網:https://chakra-ui.com/
- 架構模式:單倉庫、多包、多元件,MonoRepo;
Charkra-ui 的實現上,對於 Input、Select 等基礎的 ui 元件和常規的元件庫沒什麼太大差異,而對於 Modal、Tooltip、Popover 等存在較多自定義渲染場景的元件,採用可插拔的子元件組合的形式實現,例如 Modal :
在實現上,Modal 不作為實際掛載 dom 的 ui 元件,而是作為容器層分發 props(通過 context 實現),而實際渲染 UI 的部分以子元件來承載,每一個子元件作為一個實體接收容器層派發的 props 處理相對應的 ui 展示和事件,以 ModalCloseButton 為例:
在樣式系統的設計上 Charkra-ui 也比較有意思,首先在 css 的使用上,Charkra-ui 採用基於 @emotion/styled 的 StyledComponents。在元件的實現中,通過子包 @chakra-ui/system 下的 factory 函式為每一個原生 dom 元素或者其他元件轉換成 StyledComponents。這裡以 Input 為例:
而對於 StyledComponents 所需要的基本樣式,Charkra-ui 提供了 theme 這個子包作為元件庫預設的樣式主題,主題包內以 JS 物件形式定義了每一個元件所需要的樣式。
然後,為了讓預設主題和自定義主題注入到元件內,Charkra-ui 提供了 Provider 來注入主題樣式
而 StyleComponents 的使用也使得我們無法合理的從外部覆蓋元件內的樣式,Charkra-ui 也給我們提供了 className 的 props。
MUI
- 倉庫:https://github.com/mui/materi...
- 官網:https://mui.com/zh/
- 架構模式:單倉庫、單包、多元件;
在元件的實現上和 Charkra-ui 很類似,在樣式系統中也是使用的基於 @emotion/styled 的 StyledComponents。
Headless-ui
- 倉庫:https://github.com/tailwindla...
- 官網:https://headlessui.dev/
- 架構模式:單倉庫、單包、多元件;
Headless-ui 其實相當於 antd 依賴的 react-component(rc),只關心 dom + 邏輯實現,UI 樣式實現交給使用方;
Radix-ui
- 倉庫:https://github.com/radix-ui/p...
- 官網:https://www.radix-ui.com/
- 架構模式:單倉庫多包多元件,MonoRepo;
Radix-ui 相比於 Charka-ui 在 Select 這種最基本的資料響應元件也採用了可插拔的子元件形式,也就是說,在 Radix-ui 的實現上,子元件的粒度更輕更原子, 以 Select 為例:
在實現上,同樣採用容器層通過 context 收集 props,子元件作為 UI 響應 容器層派發的 props。
同樣,Radix-ui 也是主張無樣式的元件庫。
React-Spectrum
- 倉庫:https://github.com/tailwindla...
- 官網:https://react-spectrum.adobe....
- 架構模式:單倉庫、多包、多元件,MonoRepo;
React-Spectrum 在實現上由三部分組成:
- React-Aria:將元件的行為、可訪問性、國際化等可重用的邏輯分離並以 hooks 的形式提供;
- React-Stately:將元件內部使用的一部分狀態拆分出 hooks ,以便邏輯重用;
- React-Spectrum:結合 React-Aria、React-Stately、DOM 實現基礎元件;
相比於以上元件庫,React-Spectrum 不僅將元件從 UI 側以子元件的形式細粒度的拆分,還在邏輯側拆分出 React-Aria 和 React-Spectrum 提供 hooks 來實現邏輯拆分解耦。同理,子元件解耦也是通過 context 形式實現。
在樣式系統上 React-Spectrum 不建議使用方通過 classNames 或者 styles 來實現覆蓋樣式的邏輯,因此我們在元件上看不到這兩個 props,React-Spectrum 認為由於樣式覆蓋會導致後續的元件升級帶來不可預料的問題。