最近公司推起了共用 UI 元件化的大潮,建立了一個新的 Repo 來放置共用的 UI 元件,比如下拉選單等。出於對歷史版本的表單元件的不滿,我從兩週前開始踏上了自己的 React 表單元件製作之路,踩了不少坑也有了不少感悟,之後也會寫一篇文章關於我是如何寫這個元件的(對 React 感興趣的可以點這裡 Hyo ,中文文件)。之後公司群裡分享了這麼一個演講視訊:Best Practices on building a UI component library for your company (David Wells) – FSF 2016 ,看完之後感觸良多,本文算是該演講內容的一個梗概與探討。
回到正題,如果你在一個大型團隊工作,或者你的企業有許多部門,你們該如何實現全域性 UI 元件來跨越各個板塊的界限?想象一個場景,如果你的整個公司都在使用同一段 UI 程式碼處理公共元件,財務工具在使用它,部落格工具在使用它,線上聊天工具在使用它,且無論是在移動端,桌面端還是 web 端都能見到它,那該有多便利?無需累贅而繁複地一遍又一遍實現功能類似的表單,按鈕或是列表。這是一個相對理想的設定,因為無論你在哪兒你都只需要維護一個程式碼庫,且所有全域性資源也都在同一個地方,開發者們可以方便地找到所需的程式碼並對其貢獻。同時,共享 UI 元件同事也會給你的使用者帶來相容的體驗,無論他在瀏覽或使用哪個工具,移動端或是桌面端,他的所見所感都是相一致的。注意的是同步這一概念,對於擁有很多產品的公司來說如果共享 UI ,那就意味著一次 UI 升級整個公司的產品都會受其影響。這從大部分意義上來說是一件好事,但有時又會帶來很多麻煩,之後會說到。同時你的元件也應當是完備的,不與任何你所在團隊所使用的第三方包相沖突。綜上,如何設計你團隊產品的 UI 架構,使得其兼具上述優點的同時還有良好的可擴充套件性與效能,是我們今天所要討論的重點。
在本文中,我們主要使用 React 作為UI元件化的例子,將頁面細分元件化是 React 的核心哲學,我們可以非常方便的將本文中的理念引入其中。
我們先來看一個關於 UI 設計指南典型的例子,這是 Salesforce 的 UI 庫 Lightning Design System,他給了非常詳細的 UI 規則,各個產品的頁面、元件應當如何被處理,非常值得我們學習與借鑑。先來看一個 style guide 應當有些什麼內容:
- 文件 由你團隊成員所寫的,如果使用 React 你可以直接通過註釋 Proptypes 的方法,通過 React-docgen 生成文件。
- 程式碼預覽 你應當提供一個可以讓開發者實時除錯程式碼的地方,使其他這些元件的使用者可以更好地理解各個props 。
- 使用例項 提供一些如何將其資料匯入 UI 的例項程式碼,使其他開發者可以更快上手與他們的使用情況。
- 相容性 譬如怎樣使用警告、載入資訊等額外內容的規範,來提供使用者相一致的體驗。
那麼,如何搭建一個元件庫呢?為了回答這個問題我們可以將其細分為如下幾個小步驟:
- 將整體的問題拆開成細目。
- 如何處理 CSS ?
- 如何將資源如變數,圖示等公有化?
- 如何將其打包,便於使用?
- 開始從中獲取收益!
首先,我們現講如何將問題拆解,我們應當將頁面上顯示的一切看作是元件。也就是說每當你拿到設計師的稿件,第一件事應當就是講頁面上一切你所能看到的元素翻譯成無數個小元件,這也是 React 的理念:複用元件。
下一步,我們再將小元件組合成為較大的元件。這裡不得不提到 Brad Frost 所提出的 Atomic Design。它所闡述的理念與本文所要說的觀點相似,即由“原子”組成“分子”,“分子”構成“組織”,從而形成模板,進而生成頁面。看下這些例子,標籤,輸入,按鈕各是一個“原子”,合在一起即成了一個“分子”。
其次, CSS 一直以來都是一個非常棘手的問題。我們應當如何處理類名衝突?如何使用第三方庫的 CSS 檔案?如何保證與 CSS 檔案不衝突?如何確保相互獨立?對於這些問題現在已經有了不少解決方案。
David Wells 在演講中提到的與我們公司現在所使用的都是通過 PostCSS + CSS modules的解決方案。關於 CSS Modules 搭配 React 的用法,這裡有個很好的例子:Modular CSS with React 。具體來說可以見此用例:
1 2 3 4 5 |
/* Thumbnail.jsx */ import styles from './Thumbnail.css'; render() { return (<img className={styles.image}/>) } |
1 2 3 4 |
/* Thumbnail.css */ .image { border-radius: 3px; } |
Hash 後生成的 HTML tag 與 CSS 看起來會是這樣:
1 2 |
/* Rendered DOM */ <img class="Thumbnail__image___1DA66"/> |
1 2 3 4 |
/* Processed Thumbnail.css */ .Thumbnail__image___1DA66 { border-radius: 3px; } |
此處將在我們 Thumbnail.jsx 檔案中通過 CSS modules 引入 CSS 檔案,再通過引入的 style 變數獲取 hash 後的 CSS 類名。
此處提到的另一個工具 PostCSS 會將你的css檔案全加上字首名以適應不同瀏覽器,解決 CSS 4 的相容性問題。
所以你真的需要使用 PostCSS 和 CSS Modules 麼?你可以問自己如下問題:你在一個團隊中工作麼?你使用第三方庫的 CSS 檔案麼?你的產品在第三方環境中執行麼?你想要你的產品在任何環境中體驗一致麼?如果你回答是,那你可以選擇嘗試這一方案,因為這一解決方案基本解決了類名衝突與瀏覽器相容的問題。
第三,關於如何處理共享資源。對於全域性變數與 mixin ,我們建議通過幾個 PostCSS 的外掛來解決,而非使用sass等前處理器語言。可以通過一個簡單的Postcss config來解釋:
1 2 3 4 5 6 7 |
var vars = require('postcss-simple-vars'); var mixins = require('postcss-mixins'); var postCSSConfig = [ mixins({ mixins: require('./mixins') }), vars({ variables: require('./variables') }), ] |
此處我們從一個 mixins.js 檔案中提取全域性mixin,一個 varibles.js 檔案中提取全域性變數,他將會在你所有通過 PostCSS 編譯的 CSS 檔案中生效。實際使用中與less和sass十分相似,見例:
1 2 3 4 |
.hyo { @mixin MarsPower; /* 在 mixin.js 檔案中定義 */ color: $MarsRed; /* 在 variables.js 檔案中定義 */ } |
對於圖示,由於其輕量型與便利性我們一般選擇 svg。基本的工作流程是由設計師製作 svg ,在你的 JS 程式碼中引入 svg ,通過 ’webpack-svgstore-plugin’ 優化 svg 並生成 sprite ,將 sprite 注入 DOM 。此處我們可以使用 svg use 標籤,形式如:
1 |
<svg><use id=“icon-aaa”></svg> |
第四步,也是最後一步,我們應當如何搭建以及打包?
首先,我們有非常多的工具來使得開發 React 元件變得令人心情愉悅,推薦幾個常用的第三方庫:
carte-blanche 這是個非常牛b的 React 開發工具,只需簡單幾步就可已在瀏覽器中測試你的 React 元件,同時還支援隨機生成 prop 來測試你的元件會不會應為 prop 異常而崩潰。
react-storybook 這是一個 carte-blanche 非常相似的選擇,也是我用來開發 Hyo 的工具。
react-docgen 文件生成工具,你可以直接匯入一個react資料夾,如果你在proptype中謝了註釋它將會自動為你生成文件。
在版本更新時,注意使用語義化版本。每當你要開始寫一個新的組建時,可以先在 Github 上搜尋一下前人的實現方法,站在巨人的肩膀上做事才會事半功倍。
最後,關於如何打包,DW 推薦的方法是如下檔案結構:
對此我表示很贊同。分開打包每個小的元件入口,扁平化你的檔案結構,元件之間可以相互依賴使用,對於開發效率與擴充套件化能力十分有幫助。
有些時候很難去查閱你在哪個頁面使用了哪個元件。這裡你可以使用一個監視器元件來查詢你使用的元件。這在很多時候非常便利,譬如你想升級某一個元件,你會想去了解哪些頁面或是元件依賴了這個元件從而進行修改,DW 提供了一個實現,我們可以根據我們的需求來實現自己的 Monitor Component。
最後的最後,小結幾個開發 React 元件中常遇見的問題。(至少我是碰到了- -)首先,做之前想明白要實現的 Feature,與設計師討論並寫下自己的需求,市面上是否有可用的替代品,儘可能不要定義過多的 prop,不然在之後維護會非常辛苦。其次,儘可能地給予使用者客製化的權利,譬如內容如何渲染,排序如何進行等,最好開放一個 api 使得使用者可以自己定義,因為你永遠無法預測並滿足所有使用者的需求。第三,在所有瀏覽器上進行測試,老生常談了但有時還是會忘。最後,從開始便定義好 lint 的規則並遵從它,可以參考 AirBnB 的配置作為起始點。
囉囉嗦嗦寫了這麼多,希望大家都能從中能收穫些許。最後再安利一下 Hyo,也算是自己的第一個認真做的開源專案,希望大家多多點星! | Demo點這裡 | 文件點這裡 |