封装html ui 控件,聊聊前端 UI 组件:组件设计
本文首發于歐雷流。由于我會時不時對文章進行補充、修正和潤色,為了保證所看到的是最新版本,請閱讀原文。
在本系列文章《聊聊前端 UI 組件:組件體系》中初步說明了 UI 組件的架構設計,本文將在此基礎上進一步展開說說那篇文章中一筆帶過的部分,并闡述在設計一個 UI 組件時應該注意的點有哪些。
目錄結構
在《聊聊前端 UI 組件:組件體系》中列出的目錄結構的基礎上做了些許調整——component
├── demo # 示例相關文件
│ └── ...
├── test # 測試相關文件
│ └── ...
├── style # 樣式相關文件
│ ├── _functions.scss # Sass 函數(可選)
│ ├── _properties.scss # CSS 自定義屬性(必需),風格組件的一部分,供外部運行時自定義主題風格
│ ├── _variables.scss # Sass 變量(必需),風格組件的一部分,供外部編輯時/編譯時自定義主題風格
│ ├── _mixins.scss # Sass 混入(可選)
│ └── _rules.scss # CSS 規則(必需),視覺組件,具有約束結構的作用
├── typing # 類型相關文件
│ ├── custom-properties.ts # CSS 自定義屬性配置項(必需),用于運行時生成 CSS 自定義屬性
│ ├── aliases.ts # 類型別名(可選)
│ ├── interfaces.ts # 結構組件接口(必需)
│ └── index.ts # 類型統一導出
├── HeadlessComponent.ts # 無頭組件,UI 組件與結構無關的邏輯
├── Component.vue # 結構組件,受生成 HTML 的 JS 庫/框架的源碼、平臺限定的視圖結構描述語言影響
├── index.ts # 模塊統一導出
├── changelog.md # 組件變更記錄
├── readme.md # 組件說明文檔
├── metadata.yml
└── package.json
命名約定
HTML & CSS class
在基于組件開發(Component-based Development),即大家所說的「組件化」,在 web 前端領域普及之前,流行過一種神奇的 class 命名方式,可以說是一種方法論了——原子類(atomic classes)。
估計一入行就是 React、Vue 橫行的前端,壓根兒就沒聽過更沒見過「原子類」是個什么東西——
.w-100 { width: 100px; }
.w-150 { width: 150px; }
.h-100 { height: 100px; }
.h-150 { height: 150px; }
.m-10 { margin: 10px; }
.m-20 { margin: 20px; }
.mt-10 { margin-top: 10px; }
.ml-15 { margin-left: 15px; }
.bgc-red { background-color: red; }
.bgc-greed { background-color: green; }
.c-fff { color: #fff; }
.c-000 { color: #000; }
.f-l { float: left; }
.f-r { float: right; }
Atomic classes看到了吧,這種方法論強調的就是盡可能將 CSS 的每個屬性和值的組合拆成 class,命名方式也基本是「屬性名 + 屬性值」的形式,并且屬性名和屬性值是否進行「簡寫」以及中間有沒有 -、_ 等分隔符就看編寫的人的素養和心情了。
原子類的「優點」是,它把 class 拆分到足夠細,很好很「原子」;原子化帶來的特點就是可組合性很強,這樣任何頁面都可以通過原子類的有機組合去實現,只有想不到,沒有做不到!哪天設計師說要把按鈕距離左邊的 15 像素改為 10 像素——沒問題!把 的 .ml-15 換成 .ml-10 就好!小菜一碟!
為什么上面說的「優點」是加了引號的?我就想知道,原子類除了寫的時候字符數可能會稍微少些,跟寫內聯樣式(inline style)有什么區別?有更語義化嗎?可讀性有變更好嗎?人腦負擔有降低嗎?中、大型項目維護起來更方便嗎?
隨著基于組件開發在 web 前端領域的普及,原子類的身影逐漸消失;但最近因為某個 CSS 框架人氣走高的原因,原子類再度死灰復燃……
那么,原子類或者說樣式原子化是錯的嗎?不是,都是時臣的錯!啊,不!都是 utility-first 思想的錯!
class 應該是語義化的,尤其是在基于組件開發時,讓在視圖結構中一眼看到 class 后,就知道它是個什么東西,而不是它長什么樣。
另外,基于組件開發的特點之一就是封裝,對外屏蔽內部細節;而 utility-first 思想恰恰是暴露細節,這與基于組件開發的理念「三觀不合」。
在基于組件開發的體系下,class 理應是 component-first,即應用 CSS 組件(CSS component),那些 utility class 作為輔助存在。也就是說,當 CSS 組件自帶樣式與實際需求有些許不符時,利用 utility class 進行「微調」,而不是在外部重寫 CSS 組件的樣式——這也是一種組合方式。
比如,按鈕 CSS 組件本身是不會在水平方向撐滿容器的,但設計師想讓它占滿一行——
.Button {
display: inline-block;
text-align: center;
}
.u-block {
display: block !important;
}
CSS component
CSS 組件在本系列文章所闡述的 UI 組件體系中,叫做「視覺組件」,class 的命名遵循 BEM 的變體——SUIT CSS 命名約定。
SUIT CSS 是 Normalize.css 的作者 Nicolas Gallagher 于 2013 年左右時創立,雖然現在已經處于基本不維護的狀態了,但它基于組件開發的思想仍發揮著余熱。
SUIT CSS 命名約定我從 2014 年用到現在,并且會繼續用下去。本系列文章 CSS 相關的示例代碼中 class 的命名皆遵循此命名約定。在基于組件開發的體系下,強烈建議 class 命名遵循 SUIT CSS 命名約定——/* 組件 */
.ComponentName {}
/* 組件修飾符 */
.ComponentName--modifierName {}
/* 組件后代 */
.ComponentName-descendentName {}
/* 組件狀態 */
.ComponentName.is-stateOfComponent {}
/* 輔助工具 */
.u-utilityName {}
組件基類 .ComponentName 及其后代 .ComponentName-descendentName 很好理解,它們天然具有層級關系,共同描述了一個 UI 組件的結構——
文章標題
章節標題
章節段落
一些其他信息
文章標題
章節標題
章節段落
一些其他信息而組件修飾符 .ComponentName--modifierName 和組件狀態 .ComponentName.is-stateOfComponent 有時就不能很好地區分何時該用哪個了。就拿按鈕 CSS 組件來說,它的顏色、是否可用與尺寸,哪個該用修飾符?哪個算是狀態?
我給出一個比較簡單的判斷標準:如果是 UI 組件的特性,即不會因為什么條件而改變的,用修飾符;倘若會因某個條件滿足與否而變化,那就是狀態——
新增
批量刪除
應該注意的是,組件修飾符和組件狀態都是直接加在 UI 組件的根節點上的,也就是要跟在組件基類的后面,不能用于組件后代上。假如一個組件后代需要程序化地改變它本身的樣式,要用輔助工具類而不是狀態類。當一個組件后代的結構、功能等變得復雜時,要將其封裝成一個新的組件。
Sass 變量與 CSS 自定義屬性
在本系列文章所闡述的 UI 組件體系中,Sass 變量和 CSS 自定義屬性合稱為「風格組件」,它們負責主題風格的定制,是與設計體系(Design System)的結合點。其中,Sass 變量是在編輯時/編譯時,CSS 自定義屬性則是在運行時。
在這里,Sass 變量與 CSS 自定義屬性的命名方式比較類似,它們大概都是 -[-descendent-name|-modifier-name][-state]-(variable-name|property-name) 的形式。
由于我在基于本系列文章所闡述的思想做一套叫做「Petals」的半成品 UI 組件,因此之后的示例代碼中涉及到的 部分基本都會用 petals。
Sass 變量是以 $__petals 或 $petals 開頭,與組件名之間用 -- 連接,前者是內部使用(私有)的,上層開發者無需關心,后者是供外部在編輯時/編譯時定制用;CSS 自定義屬性則用 --petals 開頭,以 - 與組件名相連——/* 實際形式:--(variable-name|property-name) */
$__petals--button-font-size: --petals-button-font-size;
$__petals--button-line-height: --petals-button-line-height;
/* 實際形式:----(variable-name|property-name) */
$petals--button-primary-focus-color: var($__petals--primary-active-color, $petals--primary-active-color) !default;
$petals--button-primary-focus-bg: var($__petals--primary-active-bg, $petals--primary-active-bg) !default;
上文所說的 CSS 組件,即視覺組件,它是將樣式進行封裝,對外屏蔽細節;而風格組件相反,通過將視覺組件所用到的 CSS 屬性值動態化的方式達到樣式可定制化的目的,這就變得像 utility-first 的原子類一樣暴露了樣式細節。
但與 utility-first 的 CSS 框架不同的是,風格組件只給進行主題風格定制的人帶來了心智負擔,對其他的上層開發者并無影響。
業務無關
本系列文章主要討論的對象是業務無關的 UI 組件,在單說「UI 組件」或「組件」時也是指這個;而業務相關的 UI 組件,在本系列文章所闡述的 UI 組件體系中叫做「部件」。根據 UI 組件的通用性,可分為「通用組件」和「專用組件」。「通用組件」是能夠滿足大部分常規場景的 UI 組件,它們的集合通常會作為「組件庫」整體打包發布為一個軟件包;「專用組件」是為了解決某些特殊場景需求而存在的,像數據網格、各種編輯器等,這類一般都是單獨發包。
上面提到的「通用組件」和「專用組件」都是業務無關的 UI 組件。
UI 組件是什么?可以認為它是一個返回視圖結構的函數,而 UI 組件的屬性(prop)和事件(event)就是這個「函數」的參數。屬性是 UI 組件的外部與其內部進行主動通信的數據,事件則是進行被動通信的回調函數。
一個封裝得好的函數,它的參數應盡可能少,要想明白每個參數的語義,且必須確實有其存在的意義——UI 組件的屬性和事件的設計也該如此。
在設計 UI 組件的屬性時,先思考下要加的這個屬性是不是屬于這個 UI 組件本身的特性?若不是,那要加的屬性的值所對應的 UI 組件的特性是什么?如果這兩個問題都沒有得到答案,那么這個屬性可以不用加了。
UI 組件的屬性只應與其本身的特性有關,與業務意義無關——自身特性是自然特性,業務意義是附加特性。
比如,一個按鈕組件通常會有「主要」、「次要」和「危險」這幾種多少與業務沾邊的語義,那么組件的屬性該如何設計來滿足這種需求呢?
Ant Design 和 Element 的做法是將其作為 type 屬性的值或獨立成一個屬性——Ant Design 中的主要按鈕
Ant Design 中的次要(默認)按鈕
Ant Design 中的危險按鈕
Element 中的主要按鈕
Element 中的次要(默認)按鈕
Element 中的危險按鈕
按照上面說的 UI 組件屬性設計原則來看,「主要」、「次要」和「危險」作用到按鈕組件上的表現主要是顏色發生了變化,所以應該去用表示按鈕的自然特性「顏色」的 color 屬性來滿足同樣的需求——主要按鈕
次要(默認)按鈕
危險按鈕
紅色按鈕
黃色按鈕
藍色按鈕
若 UI 組件的某組特性是二元對立的,如「禁用」與「啟用」,則選擇默認不生效的那個作為屬性,且屬性值是布爾型,默認值為 false。
還是拿按鈕組件來舉例:如果默認是「禁用」,那就設計一個代表「啟用」的 enabled 屬性,其默認值是 false,只要組件在被使用時傳入了 enabled,就變成了「啟用」狀態;反之亦然。
另外,UI 組件的屬性值盡可能是簡單數據類型,也就是數字、字符串等。
業務相關
業務相關的 UI 組件,即上文所說的「部件」,因其關注點與業務無關的 UI 組件不同,所以在設計時所遵守的原則和考慮的事情也不盡相同,甚至會大相徑庭。一般來說,會用到上下文與依賴注入等技術。
由于業務相關的 UI 組件不是本系列文章主要討論的對象,在此就不展開說了。
總結
前幾天在朋友圈立了個 flag——
本文就是該 flag 的「引子」。
歡迎關注微信公眾號【Coding as Hobby】(微信中搜「coding-as-hobby」)以及時閱讀最新的技術文章~ ;-)
總結
以上是生活随笔為你收集整理的封装html ui 控件,聊聊前端 UI 组件:组件设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 50万数据生成6位数不重复字符串_JAV
- 下一篇: 如何实现tm同时监控多个状态的改变_广电