CSS – 管理

前言

CSS 有好幾種寫法. 它們最終出來的效果是一樣的, 區別只是在你如何 "寫" 和 "讀" 或者說開發和維護.

這已經不是如何"實現"的問題了, 而是代碼如何管理維護的問題. 

 

參考

CSS Utility Classes and "Separation of Concerns" (Tailwind CSS 作者的文章, 一定要看)

CSS 架構系列 – OOCSS, BEMSMACSSACSS

Is BEM Framework the best one? Share alternatives!

RSCSS — Styling your CSS without losing your sanity .

BEM Is Terrible

CSS 進化論:從CSS,SASS,BEM,CSS Modules到Styled Components (英語原文)

重新構想原子化 CSS

Youtube – 7 ways to deal with CSS

CSS 模塊化方案探討(BEM、OOCSS、CSS Modules、CSS-in-JS ...)

React拾遺:從10種現在流行的 CSS 解決方案談談我的最愛 (上), (中), (下)

 

Selector 流派

職責

HTML 只負責內容結構, 不涉及任何 "Styling" 這個是 selector 流派的重點.

所有 Styling 交給 CSS 負責.

它們之間的 connect 方式就是通過 selector.

 

Selector 方式與 class 命名

selector 要好維護, 就必須避開一些潛在的風險.

1. selector 要穩定.

比如我想給一個 icon styling. 我用 tag selector 的話, 可能是 <i> 也可能是 <svg> (fontawesome 用 <i>, 其它可能用 svg)

這樣就不太穩定, 改成 class name .icon 就穩定了. 所以一般上大家都會建議 selector 用 class name, 不要用 tag name.

但這也不是絕對的, 關鍵就是你認爲它是否穩定. 

2. class name 撞名字

selector class 是全局的, 這意味着一不小心就會撞名字了.

比如

.section-1 .title

.section-2 .title

title 就是一個重複的 class name. 但它們的樣式不一定是相同的. 因爲當我們在命名的時候, 我們的潛意識, 是有作用域概念的.

BEM

BEM 就是通過 namespace 的命名方式, 解決 class name 撞名字的問題的. 

其原理就是把名字取長一點...比如 block__element--modifier

.section-1 .section-1__title

.section-2 .section-2__title

CSS Modules

另一個方法是通過 CSS Modules, 比如 Angular omponent styles 或者 Asp.net Core – CSS Isolation

它們的原理是通過 pre-compile 動態幫你添加 namespace. 方式和 BEM 差不多, 只是 BEM 是 manual 的, 這個是 auto 的. 管理上差很大.

Descendant / Child selector (RSCSS)

.section-1 > title 

.section-2 > title 

只要確保 root selector 不要撞名字, 通過 descendant selector 是可以確保後續不會撞的. 注意: 如果有嵌套的話, 最好使用 > child selector 

不然還是有可能會撞名字的.

BEM vs RSCSS BEM 的缺點是 class name 長, descendant 的缺點是並不能 100% 做到保護, child 的缺點是沒有 descendant 那麼幹淨.

 

 

左圖是 BEM 的 HTML 頁面, class name 很長, 很醜

右圖是 RSCSS child selector 的畫面, 就是多了很多 > 箭頭, 而且層次結構需要完全與 HTML 一致 (耦合度比 BEM 大)

 

疑惑

問: class="btn-blue" 是一個合格的 class name 嗎? 

答: 不是, blue 是樣式.

 

缺點和侷限

selector 流派最大的問題就是 Styling 的複用. 按照它的標準, 一個 element 只會有一個 class name 來表示.

所有 styling 也只能寫到這個 class 上面, 那怎麼能複用呢? 

.page-title-section

.call-to-action-section

比如上面 2 個 class 是不同的東西. 但是它們都是 section

想抽象 section style 有幾個做法.

1. .page-title-section, .call-action-section 用"或者" selector 

2. 添加多一個 class 到 HTML 

class="page-title-section section" <-- 像這樣.

但這樣你就會覺得怪怪的了. 因爲 class 的作用應該只是爲了能 selector 到它. 那爲什麼要搞 2 個呢? 

其實是爲了 styling. 而我們說過 HTML 不應該被 Styling 影響.

3. 用 Sass 的 @extend 或 @include 

方法 1 和 3 是比較正確的方式, 2 有一點點偏向 Atom 流派了. 

 

僞命題

有人說, HTML 和 CSS 不要耦合, 要分開.

1. 當你修改 HTML 的時候不要影響到 CSS

2. HTML 可以獨立複用, CSS 也可以獨立複用.

我想說的是, 不要去搞這些東西. 絕大部分的時候 HTML, CSS 甚至 JS 是密不可分的.

這也是爲什麼 React, Tailwind CSS 最終會把 3 劍客寫成一團.

另一個話題是, 寫成一團好不好, 看上去確實亂, 但是也沒有亂到不能管理.

像 Angular, 它不直接在 HTML 寫 TS, 但它需要搞指令來完成那些 TS 做的事兒. 比如 *ngIf *ngFor 

這算是一個 trade-off. 儘可能不要那麼亂, 但也限制了一些靈活.

 

Atom 流派

從上面的開發過程, 我們可以看到絕大部分的情況. CSS 的職責是作爲 HTML 的 makeup.

CSS 的 selector 就是一個爲了 connect 而誕生的東西. 

如果我們直接把 CSS 寫進去 HTML 那麼就不需要這些 connect 了.

Inline CSS

inline CSS 是一個解決方案, 它幾乎就是把 CSS 的 Style "搬" 到了 HTML 而已.

這個價值不夠大. 

Atom Class

把每一個小小的 Styling 變成一個 class

class="py-3" 相等於 padding-block: var(--spacing-3)

這個做法比 inline 好了不少.

有一點點像是重新定義了 CSS 語法.

它的特點是 shorthand key/value,

padding-block 很長, 寫成 py 就好. 

var(--spacing-3) 很長, 改成 -3 就好了.

優缺點, 有點是寫的很爽. 短嘛.

缺點是, 讀的時候有點累, 短嘛.

經過一輪昇華, 價值大了一些

Tree Shaking

CSS 一直寫就會越來越大, 如果把它變成 Atom Class 那麼你增加的就不是 CSS 而只是 HTML 上的 class name 而已了.

通過 pre-compile 技術可以把沒有用到的 class 排除. 這樣就可以儘可能的少 Style 了.

價值多提升了一點點

Pre-compile = whatever

TypeSciprt 是典型的 pre-compile 神器. 任何可以 pre-compile 的東西都可以重新定義一個寫法.

這就好像高級語言最終會生成低級語言一樣. 高級語言的好處就是開發快, 維護方便. 

Tailwind CSS 也是類似的東西. 代價就是學習成本和侷限性而已. 

 

對 CSS Utility Classes and "Separation of Concerns" 的整理

這篇文章寫了很多重點. 這裏做一個整理

Phase 1: "Semantic" CSS

這個是 selector 流派. 

開發步驟是

1. 寫 HTML, 定義 class name 依據內容 (不包含 styling)

2. 寫 CSS, selector + styling 

CSS 的結構會和 HTML 高度一致. selector 利用 descendant / child selector 方式避免撞名字.

Phase 2: Decoupling styles from structure

Phase 1 的問題在於嚴重依賴結構, 以至於 HTML 稍微改動一下, CSS 也必須修改.

我個人是覺得還好. 就一起改唄. 但是有些人認爲不太好. 於是他加入了 BEM

BEM 會把 CSS 結構 "打平". 不再有嵌套, 也不用 descendant / child selector. 改用長長的 class name 做 select.

由於強制結構只有一層, 所以 HTML 和 CSS 的結構偶爾就沒有了. 

Dealing with similar components

上面的方式還不錯了. 但是遇到類似的組件就完了.

作者提了 3 個方案

1. duplicate style (這個是開玩笑的)

2. @extend, @include

3. 抽象 class 這個是大部分 library 封裝的方式 (可以參考 Swiper.js 的封裝和調用)

到這裏就是一個關鍵的轉捩點了. 之前都是先有 HTML 然後 CSS makeup style (HTML 在前, 它不需要 "認識" CSS, 相反 CSS 在後需要 "認識" HTML)

但現在 CSS Style 要被複用了. 對於第二個 HTML 來說, CSS 反而在前, HTML 在後了. 

下面這段講出了重點

"Separation of concerns" is a straw man

HTML 和 CSS 的關係是很密切的. 強行分開它們是不正確的思路.

反而要理解它們的依賴關係.

1. CSS that depends on HTML

這個就是上面提到的, 先寫 HTML 然後 CSS makeup HTML

2. HTML that depends on CSS

這個就是上面提到的, CSS 複用, 先 CSS 然後 HTML 配合.

不同的人會選擇不同的路線.

Phase 3: Content-agnostic CSS components

作者傾向於第 2 條路, 讓 CSS 複用.

後來他意識到, 組件越完整越難被複用.

於是就加入了 atom css 的概念. 後續的我就不寫了, 大致上就是加入了更多其它 "價值" 推出了 Tailwind CSS.

 

對 Youtube – 7 ways to deal with CSS 的整理

同時參考了: CSS 模塊化方案探討(BEM、OOCSS、CSS Modules、CSS-in-JS ...)

1. pure css (selector 流派), 問題撞名字

2. BEM 引入 namespace 名字就不撞了.

3. CSS Modules 用 pre-compile 自動做 namespace 

4. CSS in JS 用 JS 來寫 CSS. React 用 JS 寫 HTML 後得到了啓發, 也可以用 JS 去寫 CSS 這樣甚至取代了 Sass 的作用.

5. styled-components 是 CSS in JS 的實現庫.

6. Tailwind CSS atom CSS 的框架. 同時它也有取代 Sass 部分能力的能力.

 

總結

方案真的是好多啊. 我個人的經驗是這樣的.

1. Angular

Angular 自帶 Component styles, 所以不需要搞 BEM, CSS Modules 那套

它是用 Sass 的, 所以也不搞 CSS in JS 那套.

至於你要不要用 Tailwind CSS 這個倒是可以考慮的.

2. React / Vue

據說 CSS in JS 就有十幾個 library 在做. 這就是生態繁榮吧. php 框架也是有幾十個.

如果喜歡做選擇, 那麼可以都嘗試看看

3. ASP.NET Core 傳統 way

Sass + BEM 或者 only Tailwind CSS 也是不錯的做法.

 

我目前的做法 (ASP.NET Core 項目): 

我目前的做法是 Phase 1: "Semantic" CSS

步驟是這樣的.

1. 寫 HTML

2. 寫 CSS class (CSS 結構和 HTML 完全一致, 只是添加了 class 命名)

3. class 命名沒有用 BEM, 用的是 RSCSS Descendant / Child selector 的方式, 加一點自己的規範, 防止撞名的同時儘量確保 class name 短 (我不能接受 class name 長長...)

4. 遇到複用的 Styling 用 "或者" selector 和 @extend @include 就解決.

5. 遇到組件複用的話, 會把 HTML, CSS, JS 做成組件. 類似 Swiper library 加上 Razor 的 View Component.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章