家好,很高興又見面了,我是"高級前端分享",由我帶著大家一起關注前端前沿、深入前端底層技術,大家一起進步,也歡迎大家關注、點贊、收藏、轉發!
最近讀了一篇抵制 css-in-js 的文章,雖然大部分觀點都有道理,但部分存在可商榷之處,讓我們分析一下這篇文章,了解 css 還做了哪些努力,以及 css-in-js 會如何發展。
作者認為,模塊化 jsx 讓 html 結構與行為耦合在一起是很有價值的,然而樣式卻不應該與模塊耦合起來,因為樣式是一種全局行為。許多時候需要對網站進行全局的設計,將樣式分散到模塊中會導致更多的理解成本。
將樣式與模塊松耦合,系統會獲得更大的自由度與拓展性。如果樣式與結構松耦合,一套看似相似的的元素,可能擁有完全不同的底層結構。然而交互必須與結構緊耦合,因為交互依賴于結構。
局部樣式會阻礙視覺一致性,只有全局化樣式才能保證視覺一致性。
如果每個組件維護自己的樣式,那么會存在許多樣式代碼復制粘貼的問題,復制粘貼的代碼可維護性極低。
無論是 css-in-js 還是 css 預編譯的嘗試,各自都具有強大優點,本文對 css-in-js 提出的質疑我認為是欠妥當的,下面談談 css-in-js 如何解決作者提出的問題,以及簡單介紹 OOCSS, SMACSS, BEM, ITCSS, 和 ECSS 的思路。
文中提出,網站樣式要從全局考慮,模塊化樣式行為的優點是解決了樣式沖突問題,但因此也削弱了對全局樣式的把控。
開發單個組件的樣式分為兩種情況,分別是明確風格的組件與樣式獨立的組件,在樣式獨立組件中,由于不確定會被哪些主題的網站所引用,因此無論是全局 css 還是局部 css,都無法控制樣式。在明確風格的情況下,可以先把此風格的基色確定下來,無論是抽成 sass 變量還是 js 變量,都具有可復用性。
全局 css 的開發,適合自上而下控制,組件通過定義 class 而不需要關心具體樣式,通過全局 class 統一調控整體風格。而 css-in-js 是自下而上的,但需要預先抽出整體風格的樣式模塊,其效果與全局 css 是等價的。
全局 css 控制風格:
<style>
.container{}
.list-item{}
.submit-button{}
</style>
<div className="container">
<div className="list-item"></div>
<div className="list-item"></div>
<div className="submit-button"></div>
</div>
css-in-js 風格:
const CommonContainer = styled.div``
const CommonListItem = styled.div``
const CommonSubmitButton = css``
export const Container = styled(CommonContainer)``
export const ListItem = styled(CommonListItem)``
export const CommonSubmitButton = styled.div`
${CommonSubmitButton}
`
而 css-in-js 運行時的樣式解析,讓我們更輕易的切換主題。比如我們抽出一個公共樣式包,業務代碼中的色值都從此樣式包中引用,那么在不同的環境下,公共樣式包可能通過所在宿主環境的判斷,返回給業務代碼不同的色值,甚至與宿主環境配合,從宿主環境拿到注入的顏色,實現一套代碼在運行時輕松換膚。
文中觀點提出,css-in-js 這種局部樣式行為,會導致公共樣式、方法難以復用,導致各個模塊參雜著大量重復代碼。因為 sass 通過定義全局變量、mixins 方法讓樣式更具有復用性。
我覺得這是一種誤解,在 css-in-js 模式中,通過全局合理的設計,使用 js 文件存放顏色變量、公共方法、可能會復用的 css 代碼塊,其復用能力遠大于 sass。
OOCSS 成為 css 的面向對象加強版,每個 class 只處理一件事:
.size {width: 25%;}
.bgBlue {background:blue}
.g-bd2{margin:0 0 10px;}
網易 NEC 就大量使用了這種思想。
這樣的好處在于避免了 class 之間的冗余,讓我們更容易創建可復用的 class,也不會在命名上糾結。
然而,先不說 oocss 帶來的巨大零散 class 導致的維護成本,以及修改 class 導致的巨大風險,class 的本意是語義化,如果讓 class 使用一堆對象描述堆砌,我們將很難定位一個元素,也很難描述這個元素的含義。
SMACSS 認為 css 有 5 個類別:
我們通過這 5 種類別來拼湊出完整的 class,我感覺就是對 OOCSS 的進一步規范和約束。
對這 5 種類別,在命名時要加上對應前綴,分別是:
我覺得這樣在語義化的基礎上,拆分了狀態、主題、布局,著實增強了 css 可讀性。
盡可能減少適配層級,雖然增加適配層級會減少沖突發生率,但是會增加額外的閱讀負擔,以及一些 bug(舊版 ie 層級超過 255 導致樣式失效)。
像 css-modules 這種解決方案恰恰反其道而行之,通過層級避免沖突,通過預編譯解決閱讀負擔,然而在沒有預編譯的情況下,最小化適配深度原則依然是最有效的。
BEM 規范更像是 SMACSS 分類的加強版,通過 __element 表述后代,--modifier 表述狀態,比如:
.article {}
.article__label {} /* label 元素 */
.article__label--selected {} /* label 元素處于被選中狀態 */
類似 SMACSS 對 css 元素進行了分層:
ITCSS 的分層是非常有借鑒意義的,即便在 css-in-js 設計中,也可以參考此模式定義結構。
ECSS 的規范是這樣的:
.nsp-Component_ChildNode-variant
例子:
<div class="tl-MediaObject">
<a href="#" class="tl-MediaObject_Link">
<img class="tl-MediaObject_Media" src="mini.jpg" alt="User">
</a>
<div class="tl-MediaObject_Attribution">@BF 14 minutes ago</div>
</div>
更多細節可以看此 PPT
雖然我認為這篇文章提出的 css-in-js 缺點大部分存在漏洞,但它警示了我們,css 設計的初衷是全局化控制樣式,即便產生了樣式沖突、混亂的問題,但我們仍要記住,在模塊化開發的今天,仍要保持網站風格的整體性,即便使用了 css-in-js 的開發方式。
雖然作者呼吁我們不要只顧著 css-in-js,要放眼看看 OOCSS, SMACSS, BEM, ITCSS, 和 ECSS 等基于原生 css 的解決方案,但我覺得把這些思想運用到 css-in-js 是個不錯的選擇 :p
原文鏈接:
https://github.com/ascoders/weekly/blob/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF/27.%E7%B2%BE%E8%AF%BB%E3%80%8Acss-in-js%20%E6%9D%80%E9%B8%A1%E7%94%A8%E7%89%9B%E5%88%80%E3%80%8B.md
CSS-in-JS是什么,看到這個詞就能大概猜到是在JavaScript里寫CSS,那為什么要在JavaScript里寫CSS呢,像之前一樣寫在css文件里哪里不好么?
在介紹這個概念之前,先來回顧一下在日常編寫CSS代碼時都有哪些痛點:
全局污染 – CSS的選擇器是全局生效的,所以在class名稱比較簡單時,容易引起全局選擇器沖突,導致樣式互相影響。
命名混亂 – 因為怕全局污染,所以日常起class名稱時會盡量加長,這樣不容易重復,但當項目由多人維護時,很容易導致命名風格不統一。
樣式重用困難 – 有時雖然知道項目上已有一些相似的樣式,但因為怕互相影響,不敢重用。
代碼冗余 – 由于樣式重用的困難性等問題,導致代碼冗余。
在CSS的進化歷史上,出現過各種各樣的框架致力于解決以上的問題:
SASS, LESS – 提供了變量、簡單函數、運算、繼承等,擴展性、重用性都有了很大的提升,解決了一些樣式重用冗余的問題,但是對于命名混亂問題的效果不大。
BEM (.block__element–modifier) – 比較流行的class命名規則,部分解決了命名混亂和全局污染的問題,但class定義起來還是不太方便,比較冗長,而且和第三方庫的命名還是有可能沖突。
CSS Modules – 模塊化CSS,將CSS文件以模塊的形式引入到JavaScript里,基本上解決了全局污染、命名混亂、樣式重用和冗余的問題,但CSS有嵌套結構的限制(只能一層),也無法方便的在CSS和JavaScript之間共享變量。
可以看一個簡單的CSS Modules例子了解一下:
生成的dom結構如下圖,基于css文件中的class名稱生成了唯一的class名稱,樣式會定義到生成的class上。
styles打印出來如下圖,定義了css中的class名字和生成的唯一class名字的對應關系。
可以看出,以上框架都解決了不少痛點,但也還是各有一些不足,當然CSS-in-JS也并不是完美的解決了所有問題,我們先來詳細介紹一下。
現在隨著組件化概念的流行,對從組件層面維護CSS樣式的需求日益增大,CSS-in-JS就是在組件內部使用JavaScript對CSS進行了抽象,可以對其聲明和加以維護。這樣不僅降低了編寫CSS樣式帶來的風險,也讓開發變得更加輕松。它和CSS Modules的區別是不再需要CSS樣式文件。
來看一下幾個流行的CSS-in-JS框架六個月內的下載趨勢:
我們來看看幾個下載量靠前的框架的風格是什么樣的:
styled-components
先來看看下載量最高的styled-component的代碼風格:
從上圖可以看出,Title和Wrapper都是框架包裝好的component,可以直接在react的jsx語法中使用,在包裝component的時候還定義了標簽分別是h1和section。此段代碼產生的html dom如下圖所示:
可以看到section和h1上分別生成了唯一的class名稱,樣式也對應的定義在生成的class上了。
這樣就可以解決命名混亂和全局污染的問題。組件相關的代碼都在一起,可以統一查看,也可以方便的重用樣式。
glamorous
再來看看glamorous,這個框架是PayPal開發的。(前兩個logo看下來,恍惚間感覺進了化妝品專柜)。
和styled-component不同的是,glamorous的樣式直接以attribute的形式定義在了dom上,之后雖然也為其生成了class名稱及樣式,但這種以attribute定義的方式對偽類選擇符(如 :hover)支持的不好,會帶來一些不方便,而且需要再記住一套attributes名稱和值與真正的css樣式代碼的對應關系。
JSS
和上面兩個框架類似,jss也是會定義styles對象,并附到component上,最后生成的dom也是會有生成的唯一class名稱,并有對應的樣式,但樣式并不是真正的css語法,而是對象的屬性和值,這樣也是對偽類選擇符支持的不好,而且也需要記住屬性和css樣式代碼之間的對應關系。
Radium
Radium在定義樣式對象上看似和其他相似,但在生成dom結構的時候并沒有生成唯一的class名稱,而是直接把樣式放到了style屬性上,這樣會帶來諸如可讀性差、CSS權重過大、不支持偽類選擇符等問題。
下面再來看一個styled-component提供的基于jest的測試框架:
jest-styled-components
這個框架主要是通過生成Snapshot并比較的方式來保證component樣式的每次更改都會被檢測到,并且樣式是期望的樣式。這樣就又降低了重構CSS樣式帶來的風險。
看了這些框架后,可以發現CSS-in-JS的優勢還是挺多的:
因為有了生成的唯一class名稱,避免了全局污染的問題
唯一的class名稱也解決了命名規則混亂的問題
JavaScript和CSS之間可以變量共享,比如一些基礎的顏色和尺寸,這樣再當需要在JavaScript里計算一些高度的時候,可以取到和dom相關的一些padding,margin數值,統一管理
只生成頁面需要用到的代碼,縮減了最終包的大小,提升了性能
CSS的單元測試增加了樣式重構的安全性
但是CSS-in-JS也存在著一些不足和爭議:
有些觀點覺得JS和CSS的關系沒這么近,把CSS寫進JS里引入了新的一套依賴,增加了復雜度,新人加入項目后需要學習的東西就更多了,也讓學習曲線更加陡了
對前端框架確實有些依賴性,更適合于組件化的框架,如React等
Debug的時候需要花更多的功夫才能找到對應的樣式代碼
覆蓋第三方插件樣式時會有權重不夠的問題
Lint工具對于JavaScript內部的CSS代碼樣式支持的還不夠
在ThoughtWorks最新一期的技術雷達(CSS-in-JS | Technology Radar | ThoughtWorks)里,它的等級是Assess,表示的是:“值得追求。重要的是理解如何建立這種能力。企業應該在風險可控的項目中嘗試此技術。” 所以最后想說的是,雖然它還是有些不足和爭議,在應用之前需要多角度衡量一下對項目的適合度。但它的優點也很多,確確實實解決了很多痛點,而且與web組件化的方向高度一致,希望大家在條件合適的情況下多多嘗試,多多反饋,這樣也能促進整個CSS編碼體驗的繼續進化~
文/ThoughtWorks張霄翀
原文鏈接:https://insights.thoughtworks.cn/css-in-js/
趣是最好的老師,HelloGitHub 就是幫你找到興趣!
分享 GitHub 上有趣、入門級的開源項目。
這是一個面向編程新手、熱愛編程、對開源社區感興趣 人群的月刊,月刊的內容包括:各種編程語言的項目、讓生活變得更美好的工具、書籍、學習筆記、教程等,這些開源項目大多都是非常容易上手,而且非常 Cool。主要是希望大家能動手用起來,加入到開源社區中。
在瀏覽、參與這些項目的過程中,你將學習到更多編程知識、提高編程技巧、找到編程的樂趣。
最后 HelloGitHub 這個項目就誕生了
以下為本期內容|每個月 28 號發刊
1、ngx_waf:一個 Nginx 防火墻模塊。我差點就錯過了的寶藏項目,它使用簡單不需要復雜的配置,支持的功能直戳我的痛點。你看:
2、fast-cpp-csv-parser:讀取 CSV 文件的 C++ 庫(僅頭文件)。示例代碼:
# include "csv.h"
int main(){
io::CSVReader<3> in("ram.csv");
in.read_header(io::ignore_extra_column, "vendor", "size", "speed");
std::string vendor; int size; double speed;
while(in.read_row(vendor, size, speed)){
// 對 ram.csv 文件中的數據,做你想做的事情吧!
}
}
3、UNO:使用 C++ 編寫的命令行 UNO 紙牌游戲。操作方便支持人機或聯機對戰,游戲基于 Asio 網絡庫和現代 C++ 開發,也有對 C++17 的嘗試。分別實現了服務端、客戶端,代碼簡單對 C++ 新手友好,UNO 的愛好者快來玩一玩吧!
4、godot:一款功能豐富的開源游戲引擎。最初它只是一款 2D 引擎,近期拓展了 3D 部分的能力。相較于 UE4 或者 Unity 這樣的成熟商業引擎來說,Godot 還很年輕不夠成熟,尤其 3D 方面的能力。但它擁有簡易的開發方式,上手簡單。而且社區活躍、文檔覆蓋全面、有較為豐富的示例代碼,對于剛入門的游戲開發者友好。同時開源引擎底層代碼完全開源,開發者可以閱讀和貢獻代碼,而不是只停留在游戲邏輯開發層面。總而言之 Godot 是一個極有潛力的游戲引擎,推薦給想學習游戲開發的同學
5、water.css:一個專門為簡單頁面和示例網頁準備的 CSS 框架
6、fyne:一款 Go 語言跨平臺 UI 庫。想用 Go 寫圖形界面應用的小伙伴,快速上手:
安裝
$ go get fyne.io/fyne
運行一個 demo
$ go get fyne.io/fyne/cmd/fyne_demo/
$ fyne_demo
7、golearn:Go 寫的機器學習框架。來,跑個模型試試吧:
cd $GOPATH/src/github.com/sjwhitworth/golearn/examples/knnclassifier
go run knnclassifier_iris.go
8、keepass2android:一個開源的 Android 密碼管理器。下載地址,功能:
9、PrettyZoo:一款 Java 寫的高顏值 ZooKeeper 客戶端桌面應用。該項目使用了 JDK11 以及 JavaFX 編寫的 GUI 客戶端,代碼量適中適合想學習 JavaFX 編寫應用的朋友。需要連接 ZK 服務端查看數據的話,手邊有這么個工具還是挺方便的。實用和顏值集一身的項目
10、vueblog:一款輕量級 Java 博客項目。基于 SpringBoot+Vue 實現并附有詳細開發文檔和講解視頻,讓剛學會 Java 的同學也能搞定。每個體面的技術人員可能都有一個自己說了算的博客吧
11、x-spreadsheet:基于 JavaScript 實現的輕量級 Web 電子表格庫。它功能齊全,包含表格的基本操作和函數等,還有詳細的中文文檔,在線嘗試
12、h5-Dooring:一款功能齊全的 H5 頁面可視化配置平臺。讓你通過可視化的方式制作出 H5 頁面,技術棧以 React 為主,后臺采用 Node.js 實現。雖然網上有很多這種工具,但本項目免費開源、功能齊全值得一試
13、Ant-Forest:基于 Auto.js 的螞蟻森林能量自動收獲腳本。它是個“綠色環保”的項目,我能從中感受到滿滿的愛和想把它做好的決心!來看看作者開發 Ant-Forest 時解決了哪些難題:
14、tui.image-editor:功能齊全的圖片編輯器。支持圖片剪裁、旋轉、涂鴉等功能,實現了 Vue 和 React 封裝的組件,便于整合進你的項目
15、windows95:基于 Electron 實現的 Windows 95 操作系統。它實現了該操作系統下的所有東西,對!所有!想體驗下 Windows 95 版的掃雷嗎?下載安裝即可
16、LuLu:免費開源的 macOS 防火墻軟件
17、humhub:用 PHP 寫的開源社交平臺。看過《社交網絡》的小伙伴,都知道大名鼎鼎的 Facebook 最早就是扎克伯格用 PHP 語言寫出來的,humhub 能夠讓不會編程的小伙伴也可以用創建出一個社交平臺啦。跟著提示一步步操作,不到 1 分鐘我的社交平臺就建好了
18、phpbrew:一個編譯、安裝、管理多版本 PHP 的工具。有了它就可以方便地在不同 PHP 版本之間自由切換啦,特性:
19、python-patterns:Python 設計模式和使用場景的集合
20、pgcli:支持語法高亮和自動補全的 Postgres 數據庫客戶端命令行工具。它安裝簡單上手快速,如果你用過 Postgres 數據庫自帶的命令行工具,就一定能感受到 pgcli 的迷人之處
21、15-minute-apps:基于 PyQt 框架寫的小型桌面應用程序的集合。想用 Python 寫桌面應用的小伙伴,這個項目應該可以幫到你。比如寫個掃雷游戲:
22、Pine:一個免費、輕量、簡潔的 macOS Markdown 編輯器。功能:
23、Publish:專為 Swift 開發人員準備的靜態網站生成器。讓你實現整個網站都是用 Swift 構建的工具,支持多種主題、插件以及更多強大的自定義選項。示例網站,安裝和快速開始:
$ git clone https://github.com/JohnSundell/Publish.git
$ cd Publish
$ make
$ mkdir MyWebsite
$ cd MyWebsite
$ publish new
24、open-source-rover:NASA 面向科技愛好者開源的火星漫游車設計方案和代碼。通過該項目你可以使用便宜的樹莓派做出自己的火星漫游車,所需的零件很容易就可以買到,遙控部分是使用現成的 Xbox 手柄或者手機,減少花銷。喜歡動手和硬件的小伙伴們,這個東西夠酷嗎?
25、bat:替代 cat 的命令行工具。你還在命令行用 cat 查看文件嗎?那你就 out 啦!今天推薦的 bat 它不僅支持語法高亮,還能展示 Git 的改動。macOS 下安裝命令:brew install bat 相信你用過 bat 后就不會再想用回 cat 了
26、Web-Dev-For-Beginners:微軟開源的 Web 開發教程。該教程共有 24 節課,但目前只有英文版
27、neofetch:展示操作系統信息的命令行工具,支持將近 150 種操作系統
28、jpeg_tutorial:教你編寫 JPEG 解碼器的教程,示例為 Rust 代碼
29、sql-style-guide:一份 SQL 語句編寫風格建議。比如:
-- Good
select *
from users
where email = 'example@domain.com'
-- Bad
select *
from users
where email = "example@domain.com"
30、pure-bash-bible:該書有好多復制就能用的 bash 函數,我愿稱其為 bash 的“奇技淫巧”。比如把字母轉為大寫的函數:
upper() {
# Usage: upper "string"
printf '%s\n' "${1^^}"
}
$ upper "hello"
HELLO
31、pulse:根據包含馬賽克的人臉圖像,生成一張相似容貌的結果。注意不是復原哦,僅可用于人臉
32、Surface-Defect-Detection:該項目整理了目前大量靠譜的表面缺陷檢測數據集,還有最新的頂會論文以及作者的解讀筆記。從事視覺方向的小伙伴,心動了嗎?
迎留言告訴我本期你最喜歡那個項目,如果覺得本文還不錯的話,就點贊、轉發一波吧~
*請認真填寫需求信息,我們會在24小時內與您取得聯系。