「前端工程四部曲」模塊化的前世今生(下)

語言: CN / TW / HK

聲明:本文為掘金首發簽約文章,未經授權禁止轉載。

寫在前面

上文「前端工程四部曲」模塊化的前世今生(上)中我們主要介紹了JS模塊化相關的知識,對於JS模塊化,相信很多人都不陌生,但説起CSS模塊化,可能大家就沒有這麼清晰了。大部分人都會重點關注JS而忽略CSS,但CSS也是前端三劍客不可或缺的一部分,在編寫CSS的時候,絕大多數人都是比較隨意的,所以會經常造成一些開發痛點。接下來這篇文章我們就來簡單聊一聊 CSS 模塊化。

為什麼CSS也需模塊化?

其實平常我們使用 Vue、React這類框架時,就已經使用到了CSS模塊化。回顧一下歷史,從我們手寫原生CSS,到後面使用Sass/Less等預處理器,再到使用 PostCSS 等後處理器,或者是使用CSS Module以及CSS in JS,都是對標某些問題而產生的比較具有針對性的方案。

而CSS模塊化所針對的問題追溯源頭其實都很簡單:

  • Class命名重複
  • Class層級結構
  • CSS代碼複用
  • CSS文件拆分

這也是 CSS 模塊化存在的原因。

有哪些CSS模塊化方案?

BEM

BEM 是一種非常有用、強大且簡單的命名約定,可以説是一個CSS命名規範,也可以説是一種思想。它使前端代碼更易於閲讀和理解、更易於使用、更易於擴展、更健壯和明確,並且更加嚴格。

BEM的意思就是塊(block)、元素(element)、修飾符(modifier),是由 Yandex 團隊提出的一種前端命名方法論。這種巧妙的命名方法讓你的CSS類對其他開發者來説更加透明而且更有意義。BEM命名約定更加嚴格,而且包含更多的信息,它們用於一個團隊開發一個耗時的大項目。

我們可以簡單看一下這個命名約定:

css .block{} /* 代表了更高級別的抽象或組件 */ .block__element{} /* 代表.block的後代,用於形成一個完整的.block的整體 */ .block--modifier{} /* 代表.block的不同狀態或不同版本 */

上面的描述有些官方,其實 .block 就代表一個視圖中的一個塊,而 .block__element 代表的是 .block 塊下的元素 ,最後的 .block--modifier 代表的是 .block 塊的修飾符。當然也可以按照下面這種寫:

css .site-search{} /* 塊 */ .site-search__field{} /* 元素 */ .site-search--full{} /* 修飾符 */

唯一的區別是多了一個 - 符號,這個是為了讓你自己的視圖塊可以用單個連字符來界定,__ 代表元素,-- 代表修飾符。

舉個例子:

常規CSS命名寫法如下:

```html

```

BEM命名寫法如下:

```html

```

如上,常規的CSS寫法乍一看簡單漂亮,但代碼量大的時候,就會很亂,不知道 class 對應的是什麼。而 BEM 就不一樣了,雖然看上去很醜,但我們能清晰地看到有個叫 .site-search 的塊,它內部是 .site-search__fieldsite-search__button 兩個元素。並且 .site-search 還有另外一種形態叫 .site-search--full

優點:

BEM 的優點在於所產生的 CSS 類名都只使用一個類別選擇器,可以避免傳統做法中由於多個類別選擇器嵌套帶來的複雜的屬性級聯問題。BEM 命名規則中,所有的 CSS 樣式規則都只用一個類別選擇器。因此所有樣式規則的特異性(specificity)都是相同的,也就不存在複雜的優先級問題。這可以簡化屬性值的層疊規則。代碼清單中的命名規則的好處在於每個 CSS 類名都很簡單明瞭,而且類名的層次關係可以與 DOM 節點的樹型結構相對應。

缺點:

CSS 類名會比較長而且複雜,根據 BEM 命名規則產生的 CSS 類名都會很複雜,粗暴來説就是醜。不過熟悉了命名規則之後,可以很容易理解其含義。和 BEM 的實用性比起來,醜一點根本不值一提!!

OOCSS

Object Oriented CSS,面向對象的CSS,旨在編寫高可複用、低耦合和高擴展的CSS代碼。

OOCSS是以面向對象的思想去定義樣式,將抽象和實現分離,抽離公共代碼。

OOCSS 認為 container(容器) 和 content(內容) 是需要隔離開的。也就是説,儘量不要去使用依賴於節點結構位置的樣式定義。比如這樣的就是不允許的:

css .container-list .title{ /* ... */ }

OOCSS 建議:

css .title{ /* ... */ }

舉個例子大家就理解了,假如我們有一個容器如下:

```html

```

按照 OOCSS ,會為這個容器創建更多的類,並且每個樣式對應一個類,這樣是為了後面可以重複使用這些組件的樣式,避免重複寫相同的樣式。如下:

```html

```

如上,是不是很好理解?

優點:

説到 OOCSS 的優點,因為樣式重複利用,所以 CSS 代碼量自然就減少了減少,可以降低我們工作量。代碼量少,那加載速度自然也快,代碼簡潔,便於維護。能輕鬆構造新的頁面佈局,或製作新的頁面風格。

缺點:

OOCSS 的缺點也很明顯,它只適用於大型網站項目(因為重複組件,樣式多),小型項目優勢不明顯(代碼少)。而且需要巧妙運用,因為特定要求(強調重複使用類選擇器,避免使用ID選擇器)如果運用不得當,反而可能會造成後續維護困難,所以使用此方案最好寫上説明文檔。

AMCSS

AMCSS 即 Attribute Modules for CSS 。 AM 是一種使用 HTML 屬性及其值而非樣式元素的類的技術。這樣每個屬性都可以有效地聲明一個單獨的命名空間來封裝樣式信息,從而使 HTML 和 CSS 更具可讀性和可維護性。

簡單來説就是通過 CSS 屬性選擇器來模塊化 CSS。

舉個例子,如下這段代碼:

```html

Button

```

轉變成 AMCSS,由於是基於屬性控制,就變成了下面這樣:

```html

Button

```

為了避免屬性衝突,我們一般會為其加一個統一的前綴,如 am- ,所以最終就變成下面這樣子:

```html

Button

```

不要覺得奇怪,這只是對 CSS 選擇器的靈活運用而已,不瞭解的話可以 Google 一下 CSS 屬性選擇器。

優點:

屬性值得靈活性,相當於通過屬性值來增加命名空間,減少全局名稱空間,以便更好的將 CSS 模塊化。

缺點:

屬性選擇器的性能和類名相比差別很大,過度使用可能會產生可感知的性能問題。主要還是要看使用場景。

ACSS

ACSS,全稱是 Atomic CSS ,即原子化的 CSS。

用最簡單的話來説就是,把每一個單一的作用樣式定義一個 Class,確保整個樣式表沒有一條重複的樣式,這樣複用性是最高的,代碼也最少,但是每個元素就需要一堆的 Class。

如下:

```html

```

直接寫原子化的 CSS 還是比較累的,不過好在有現成框架,像 Tailwind CSS 就是一個基於 ACSS 的可定製的基礎層 CSS 框架,它提供了構建定製化所需的構建塊,無需重新覆蓋內建於框架內中的風格。設計想法基於工具類延伸,解決了工具理念的缺點。而整體框架設計,帶來很好的拓展性。最重要的是主流編輯器都有補全插件,使用成本低。

使用 Tailwind CSS 後的代碼就像下面這樣,每個塊元素上通過很多的 CSS 類來定義樣式,基本不需要我們自己定義,即使有,也只需要寫很少一部分額外的 CSS 代碼,當然,也可以對 Tailwind 做一些擴展,配合 PostCSS 等後處理器,使用起來相當絲滑,看些例子:

響應式:

html <img class="w-16 md:w-32 lg:w-48" src="...">

上面代碼中的 md 即代表 @media (min-width: 768px) { ... }lg 則是 @media (min-width: 1024px) { ... }

主題色:

```html

Dark mode is here!

Lorem ipsum...

```

如上,默認主題以及 dark 主題集於一身。

交互狀態:

```html

```

基礎樣式:

如下,還可以自定義一些基礎樣式:

```css @tailwind base; @tailwind components; @tailwind utilities;

@layer base { h1 { @apply text-2xl; } h2 { @apply text-xl; } } ```

功能類:

功能類相當於一個擁有多個類樣式的類集合,使用時要按需着重設計。

css .btn { @apply py-2 px-4 font-semibold rounded-lg shadow-md; } .btn-green { @apply text-white bg-green-500 hover:bg-green-700; }

當然,還有很多用法,不一一細説了,具體使用官方文檔説的很全面。

優點:

ACSS 的優點是可以寫基礎、視覺功能小的、單用途的 CSS,相當於把每一個單一的作用定義一個class,這點也是和 OOCSS 不一樣的地方。它確保整個樣式表沒有一條重複的樣式,這樣複用性是最高的,代碼也最少。

缺點:

使用原子化的 Tailwind CSS 框架後寫 CSS 類有點像寫行內樣式。雖然 CSS 代碼最小化了,但 HTML 膨脹了,雖然不用考慮命名,但是要記一堆新規則。所以市場對它的評價褒貶都有吧!具體大家自行體驗過後自有評判,個人還是很看好的。

CSS Modules

CSS Modules 不是一個官方的規範,也不是瀏覽器的一種機制,它是構建步驟中的一個進程。通過構建工具的幫助,將 class 的名字或者選擇器的名字作用域化(類似命名空間化)。

我們之前使用原生 CSS 時,寫兩個名字相同的 class,樣式肯定會衝突。而 CSS Modules 則會將兩個 class 名字通過 JS 手段編譯最終生成兩個不同的 class 名附加到元素上從而避免樣式衝突,相當於為 CSS 搞了一個作用域。

當然這個編譯手段有很多,但目的相同,都是讓兩個名字相同的 class 互不干擾,拿我們常用的 Vue 舉例子:

上圖即 Vue-cli 運行被編譯後的代碼,我們可以很清晰的看到,編譯後的 html 標籤帶有 data-v 開頭的自定義屬性,而 CSS 為其添加樣式時,通過類名+屬性選擇器的方法來避免命名衝突,從而實現模塊化。

而 Vue 的這種 CSS Modules 實現形式,恰好使用到了上面我們所説的 AMCSS。

既然連 Vue 都在使用這種方式,那麼它肯定有一些獨特的優勢,我們再來説 CSS Modules 的優勢在哪?

優點:

其實也可以猜到,首先 CSS Modules 只關心組件本身的命名唯一就可以了,也就相對解決了 CSS 的全局命名衝突問題。

其次也解決了 CSS 嵌套過深的問題,正式因為全局命名衝突的問題,我們不得已就要為 class 加上一些獨立的命名空間,書寫時也會多層嵌套。我們知道,CSS 選擇器的解析規則是層級越深,比較的次數也就越多,會影響整個頁面的渲染。而獨立命名空間以及嵌套也會增加不必要的字節開銷,很大程度上還伴隨語義混亂,可擴展性很不好,而我們只能進行約束,但是約束越多,擴展性越差,CSS Modules 則不同,因為分割了組件,每個組件都將作為一個獨立單元存在,很清爽。

最後,CSS Modules 也支持引入外部模塊樣式,可以共享樣式。

缺點:

要説到 CSS Modules 的缺點,我個人覺得 CSS Modules 本身沒有特別明顯的缺點,硬説起來,它對於開發可能還是缺乏一些 CSS 代碼組織方式(規範),比如 CSS 複用、單文件中 CSS 書寫方式等等,這些其實可以配合前文我們説的 BEM、ACSS 等一些約束,就可以很好的控制。

CSS in JS

CSS in JS,從名字就可以看出,把 CSS 寫在 JS 文件裏。它是一種思想,而不是某個具體庫的實現,這樣你就可以在CSS中使用一些屬於JS的諸如模塊聲明,變量定義,函數調用和條件判斷等語言特性來提供靈活的可擴展的樣式定義。這種思想也因為 React 的廣泛使用而普及。

實現 CSS in JS 的庫有很多,較為常見的應該是下面這幾個:

  • Styled-components
  • Radium
  • glamorous
  • JSS

不同的 CSS in JS 實現除了生成的 CSS 樣式和編寫語法有所區別外,它們實現的功能除了一些最基本的如 CSS 局部作用域之外還有一些獨有功能,就比如下面這些:

  • 全局選擇器
  • 基於狀態的樣式
  • 客户端與服務器端渲染
  • 緩存
  • 內置自動前綴
  • 媒體查詢
  • 選擇器嵌套
  • 內置動畫支持
  • 其他插件和軟件包
  • 等等

這裏就不一一展開説了,自行了解即可。

有些人極其討厭 CSS in JS,甚至説喜歡 CSS in JS 的人是因為其本身不懂 CSS等等,其實我也不喜歡 CSS in JS,我是 CSS Modules 的愛好者。但是這並不妨礙 CSS in JS 確實有其獨特的魅力,客觀評價那就是優缺點都有。

優點:

優點有很多,比如 CSS in JS 本質上是 JavaScript 代碼,因此我們可以將複雜的邏輯應用於樣式規則,例如循環、條件、變量、基於狀態的樣式等等, 這樣的話如果我們需要創建有動態功能的複雜 UI,它是一種不錯的解決方案;還有全局作用域、死代碼移除、生效順序依賴於樣式加載順序、常量共享等都可以説是它的優點。

缺點:

就我個人而言,最大的感受就是這玩意兒解決方案太多了,每個解決方案的語法還都不一樣,也沒出來一個大一統方案,就會讓我感覺用啥都不放心,生怕使用的方案突然停更了,這是其一。

現下這麼多 CSS 處理工具,加上 CSS Modules 方案足以很好的搞定開發,而且還是常規的 CSS/JS 分離,而把 CSS 寫進 JS 裏,那樣式就相當於被捆綁到 JS 中了,拖累性能不説,還會增加學習曲線,這是其二。

CSS 也在不斷髮展,隨着各種新的標準引入,被瀏覽器大幅度支持後,我覺得將來甚至連 Scss/Less 都不需要,完全可以使用原生 CSS 配合 CSS 處理工具以及相關規範約束搞定開發,搞太多無用的東西反而徒增煩惱,這是其三。

當然這個是因為我着實不喜歡,所以帶了點個人傾向,無傷大雅,無非就是打打嘴炮,喜歡 CSS in JS 的也不是少數,大家還是全憑個人喜好,不過它確實不太適合新手。

寫在最後

其實,BEM、OOCSS、AMCSS、ACSS這些對我們來説只是約束,早期我們就憑藉這些約束來使開發更方便,而 CSS Modules 和 CSS in JS 則是正經的模塊化解決方案。CSS in JS 是徹底拋棄 CSS,使用 JS 來寫樣式,這種方式比較激進一些,不能利用現有的 CSS 技術,處理起來偽類等問題相對困難。CSS Modules 仍然使用 CSS,只是讓 JS 來管理依賴,能夠最大化地結合 CSS 生態和 JS 模塊化能力,在我來看是最優的解決方案了。

由於 CSS Modules 以及 CSS in JS 這兩種方案伴隨着 Vue 及 React 生態而快速發展,很多人小團隊開發起來,用一些工具加上一些庫諸如 Scss/Less 或者 Styled-components 開發感覺挺好,能解決大多開發問題就完事了,所以就不太在意 BEM、OOCSS、AMCSS、ACSS 這些約束了,久而久之造成的後果就是團隊 CSS 寫的一塌糊塗,命名也千奇百怪。即使是組件內寫不了太多的 CSS ,長此以往下來也會積累很多各式各樣的樣式問題。So,我想説的是,如果你的團隊 CSS 問題如上,那就趕緊行動起來把,在現代的 CSS 模塊化方案之上再使用一些像 BEM、ACSS 等約束來把 CSS 規範起來,簡單省事,利大於弊。

如有錯誤歡迎指正!碼字不易歡迎點贊!

參考

BEM

AMCSS

BEM —— 源自Yandex的CSS 命名方法論

淺談CSS模塊化