文章由 ssh 翻譯 / 潤色,原文地址。css
隨着 Facebook 和 Twitter 最近的產品部署,我認爲一個新的趨勢正在緩慢增加:Atomic CSS-in-JS。html
在這篇文章中,咱們將看到什麼是Atomic CSS(原子 CSS),它如何與 Tailwind CSS 這種實用工具優先的樣式庫聯繫起來,目前不少大公司在 React 代碼倉庫中使用它們。前端
因爲我不是這方面的專家,因此我不會去深刻探討它的利弊。我只是但願能幫助你瞭解它的大體內容。react
先拋出一個使人開心的結論,新的 CSS 編寫和構建方式讓 Facebook 的主頁減小了 80% 的 CSS 體積。git
你可能據說過各類 CSS 方法,如 BEM, OOCSS…github
<button class="button button--state-danger">Danger button</button>
複製代碼
如今,人們真的很喜歡 Tailwind CSS 和它的 實用工具優先(utility-first) 的概念。這與 Functional CSS 和 Tachyon 這個庫的理念很是接近。web
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" >
Button
</button>
複製代碼
用海量的實用工具類(utility classes)組成的樣式表,讓咱們能夠在網頁裏大顯身手。面試
原子 CSS 就像是實用工具優先(utility-first)CSS 的一個極端版本: 全部 CSS 類都有一個惟一的 CSS 規則。原子 CSS 最初是由 Thierry Koblentz (Yahoo!)在 2013 年挑戰 CSS 最佳實踐時使用的。npm
/* 原子 CSS */
.bw-2x {
border-width: 2px;
}
.bss {
border-style: solid;
}
.sans {
font-style: sans-serif;
}
.p-1x {
padding: 10px;
}
/* 不是原子 CSS 由於這個類包含了兩個規則 */
.p-1x-sans {
padding: 10px;
font-style: sans-serif;
}
複製代碼
使用實用工具/原子 CSS,咱們能夠把結構層和表示層結合起來:當咱們須要改變按鈕顏色時,咱們直接修改 HTML,而不是 CSS!react-native
這種緊密耦合在現代 CSS-in-JS 的 React 代碼庫中也獲得了認可,但彷佛 是 CSS 世界裏最早對傳統的關注點分離有一些異議。
CSS 權重也不是什麼問題,由於咱們使用的是最簡單的類選擇器。
咱們如今經過 html 標籤來添加樣式,發現了一些有趣的事兒:
能夠確定的缺點是,html 有點臃腫。對於服務器渲染的 web 應用程序來講多是個缺點,可是類名中的高冗餘使得 gzip 能夠壓縮得很好。同時它能夠很好地處理以前重複的 css 規則。
一旦你的實用工具/原子 CSS 準備好了,它將不會有太大的變化或增加。能夠更有效地緩存它(你能夠將它附加到 vendor.css 中,從新部署的時候它也不會失效)。它還具備至關好的可移植性,能夠在任意其餘應用程序中使用。
實用工具/原子 CSS 看起來頗有趣,但它們也帶來了一些挑戰。
人們一般手工編寫實用工具/原子 CSS,精心制定命名約定。可是很難保證這個約定易於使用、保持一致性,並且不會隨着時間的推移而變得臃腫。
這個 CSS 能夠團隊協做開發並保持一致性嗎?它受巴士因子的影響嗎?
巴士係數是軟件開發中關於軟件專案成員之間訊息與能力集中、未被共享的衡量指標,也有些人稱做「貨車因子」、「卡車因子」(lottery factor/truck factor)。一個專案或計劃至少失去若干關鍵成員的參與(「被巴士撞了」,指代職業和生活方式變更、婚育、意外傷亡等任意致使缺席的原因)即致使專案陷入混亂、癱瘓而沒法存續時,這些成員的數量即爲巴士係數。
你還須要預先開發好一個不錯的實用工具/原子樣式表,而後才能開始開發新功能。
若是實用工具/原子 CSS 是由別人製做的,你將不得不首先學習類命名約定(即便你知道 CSS 的一切)。這種約定是有主觀性的,極可能你不喜歡它。
有時,你須要一些額外的 CSS,而實用工具/原子 CSS 並不提供這些 CSS。沒有約定好的方法來提供這些一次性樣式。
Tailwind 使用的方法是很是便捷的,而且解決了上述一些問題。
它經過 Utility-First 的理念來解決 CSS 的一些缺點,經過抽象出一組類名 -> 原子功能的集合,來避免你爲每一個 div 都寫一個專有的 class,而後整個網站重複寫不少重複的樣式。
傳統卡片樣式寫法:
Tailwind 卡片樣式寫法:
它並非真的爲全部網站提供一些惟一的實用工具 CSS,取而代之的是,它提供了一些公用的命名約定。經過一個配置文件,你能夠爲你的網站生成一套專屬的實用工具 CSS。
ssh 注:這裏原做者沒有深刻介紹,爲何說是一套命名約定呢而不是生成一些定死的 CSS 呢?
舉幾個例子讓你們感覺一些,Tailwind 提供了一套強大的構建系統,好比默認狀況下它提供了一些響應式的斷點值:
// tailwind.config.js
module.exports = {
theme: {
screens: {
'sm': '640px',
// => @media (min-width: 640px) { ... }
'md': '768px',
// => @media (min-width: 768px) { ... }
'lg': '1024px',
// => @media (min-width: 1024px) { ... }
'xl': '1280px',
// => @media (min-width: 1280px) { ... }
}
}
}
複製代碼
你能夠隨時在配置文件中更改這些斷點,好比你所須要的小屏幕 sm
可能指的是更小的 320px
,那麼你想要在小屏幕時候採用 flex 佈局,仍是照常寫 sm:flex
,遵循一樣的約定,只是這個 sm
已經被你修改爲適合於項目需求的值了。
在好比說,Tailwind 裏的 spacing
掌管了 margin、padding、width 等各個屬性裏的表明空間佔用的值,默認是採用了 rem 單位,當你在配置裏這樣覆寫後:
// tailwind.config.js
module.exports = {
theme: {
spacing: {
'1': '8px',
'2': '12px',
'3': '16px',
'4': '24px',
'5': '32px',
'6': '48px',
}
}
}
複製代碼
你再去寫 h-6
(height), m-2
(margin), mb-4
(margin-bottom),後面數字的意義就被你改變了。
也許從桌面端換到移動端項目,這個 6
表明的含義變成了 6rem
,可是這套約定卻深深的印在你的腦海裏,成爲你知識的一部分了。
Tailwind 的知識能夠遷移到其餘應用程序,即便它們使用的類名並不徹底相同。這讓我想起了 React 的「一次學習,處處編寫」理念。
我看到一些用戶反饋說,Tailwind 提供的類名能覆蓋他們 90% - 95% 的需求。這個覆蓋面彷佛已經足夠廣了,並不須要常常寫一次性的 CSS 了。
此時,你可能想知道爲何要使用原子 CSS 而不是 Tailwind CSS?強制執行原子 CSS 規則的一個規則,一個類名,有什麼好處?你最終會獲得更大的 html 標籤和更煩人的命名約定嗎?Tailwind 已經有了足夠多的原子類了啊。
那麼,咱們是否應該放棄原子 CSS 的想法,而僅僅使用 Tailwind CSS?
Tailwind 是一個優秀的解決方案,但仍然有一些問題沒有解決:
須要學習一套主觀的命名約定
CSS 規則插入順序仍然很重要
未使用的規則能夠輕鬆刪除嗎?
咱們如何處理剩下的一次性樣式?
與 Tailwind 相比,手寫原子 CSS 可能不是最方便的。
CSS-in-JS 和實用工具/原子 CSS 有密切關係。這兩種方法都提倡使用標籤進行樣式化。以某種方式試圖模仿內聯樣式,這讓它們有了不少類似的特性(好比在移動某些功能的時候更有信心)。
Christopher Chedeau 一直致力於推廣 React 生態系統中 CSS-in-JS 理念。在不少次演講中,他都解釋了 CSS 的問題:
實用工具/原子 CSS 也解決了其中的一些問題,但也確實無法解決全部問題(特別是樣式的非肯定性解析)。
若是它們有不少類似之處,那咱們可否同時使用它們呢?
原子 CSS-in-JS 能夠被視爲是「自動化的原子 CSS」:
你再也不須要建立一個 class 類名約定
通用樣式和一次性樣式的處理方式是同樣的
可以提取頁面所須要的的關鍵 CSS,並進行代碼拆分
有機會修復 JS 中 CSS 規則插入順序的問題
我想強調兩個特定的解決方案,它們最近推進了兩個大規模的原子 CSS-in-JS 的部署使用,來源於如下兩個演講。
React-Native-Web at Twitter (更多細節在Nicolas Gallagher 的演講)。
Stylex at Facebook (更多細節在Frank Yan 的演講)。
也能夠看看這些庫:
React-Native-Web 是一個很是有趣的庫,讓瀏覽器也能夠渲染 React-Native 原語。不過咱們這裏並不討論跨平臺開發(演講裏有更多細節)。
做爲 web 開發人員,你只須要理解 React-Native-Web 是一個常規的 CSS-in-JS 庫,它自帶一些原始的 React 組件。全部你寫 View
組件的地方,均可以用 div 替換。
React-Native-Web 的做者是 Nicolas Gallagher,他致力於開發 Twitter 移動版。他們逐漸把它部署到移動設備上,不太肯定具體時間,大概在 2017/2018 年左右。
從那之後,不少公司都在用它(美國職業足球大聯盟、Flipkart、Uber、紐約時報……),但最重要的一次部署,則是由 Paul Armstrong 領導的團隊在 2019 年推出的新的 Twitter 桌面應用。
Stylex 是一個新的 CSS-in-JS 庫,Facebook 團隊爲了 2020 年的 Facebook 應用重構而開發它。將來可能會開源,有可能用另外一個名字。
值得一提的是,React-Native-Web 的做者 Nicolas Gallagher 被 Facebook 招安。因此裏面出現一些熟悉的概念一點也不奇怪。
個人全部信息都來自演講 :),還須要等待更多的細節。
不出所料,在 Atomic CSS 的加成下,Twitter 和 Facebook 的 CSS體積都大幅減小了,如今它的增加遵循的是對數曲線。不過,簡單的應用則會多了一些 初始體積。
Facebook 分享了具體數字:
413Kb
的 CSS74Kb
,包括暗黑模式這兩個庫的 API 看起來很類似,但也很難說,由於咱們對 Stylex 瞭解很少。
值得強調的是,React-Native-Web 會擴展 CSS 語法糖,好比 margin: 0
,會被輸出爲 4 個方向的 margin 原子規則。
以一個組件爲例,來看看舊版傳統 CSS 和新版原子 CSS 輸出的區別。
<Component1 classNames="class1" /> <Component2 classNames="class2" />
複製代碼
.class1 {
background-color: mediumseagreen;
cursor: default;
margin-left: 0px;
}
.class2 {
background-color: thistle;
cursor: default;
jusify-content: flex-start;
margin-left: 0px;
}
複製代碼
能夠看出這兩個樣式中 cursor
和 margin-left
是如出一轍的,但它在輸出中都會佔體積。
再來看看原子 CSS 的輸出:
<Component1 classNames="classA classC classD" />
<Component2 classNames="classA classB classD classE" />
複製代碼
class a {
cursor: default;
}
class b {
background-color: mediumseagreen;
}
class C {
background-color: thistle;
}
class D {
margin-left: 0px;
}
class E {
jusify-content: flex-start;
}
複製代碼
能夠看出,雖然標籤上的類名變多了,可是 CSS 的輸出體積會隨着功能的增多而減緩增加,由於出現過一次的 CSS Rule 就不會再重複出現了。
咱們看看 Twitter 上的標籤是什麼樣子的:
如今,讓咱們來看看新 Facebook:
不少人可能會被嚇到,可是其實它很好用,並且保持了 可訪問性。
在 Chrome 裏檢查樣式可能有點難,但 devtools 裏就看得很清楚了:
與手寫的工具/原子 CSS 不一樣,JS 庫能讓樣式不依賴於 CSS 規則的插入順序。
在規則衝突的狀況下,生效的不是標籤上 class attribute 中的最後一個類,而是樣式表中最後插入的規則。
以這張圖爲例,咱們指望的是書寫在後的 blue
類覆蓋前面的類,但實際上 CSS 會以樣式表中的順序來決定優先級,最後咱們看到的是紅色的文字。
在實際場景中,這些庫避免在同一個元素上寫入多個規則衝突的類。它們會確保標籤上書寫在最後的類名生效。其餘的被覆蓋的類名則被規律掉,甚至壓根不會出如今 DOM 上。
const styles = pseudoLib.create({
red: {color: "red"},
blue: {color: "blue"},
});
// 只會輸出 blue 相關的 CSS
<div style={[styles.red, styles.blue]}> Always blue! </div>
// 只會輸出 red 相關的 CSS
<div style={[styles.blue, styles.red]}> Always red! </div>
複製代碼
注意:只有使用最嚴格的原子 CSS 庫才能實現這種可預測的行爲。
若是一個類裏有多個 CSS 規則,而且只有其中的一個 CSS 規則被覆蓋,那麼 CSS-in-JS 庫沒辦法進行相關的過濾,這也是原子 CSS 的優點之一。
若是一個類只有一個簡單的 CSS 規則,如 margin: 0
,而覆蓋的是 marginTop: 10
。像 margin: 0
這樣的簡寫語法被擴展爲 4 個不一樣的原子類,這個庫就能更加輕鬆的過濾掉不應出如今 DOM 上的類名。
只要你熟悉全部的 Tailwind 命名約定,你就能夠很高效的完成 UI 編寫。一旦你熟悉了這個設定,就很難回到手寫每一個 CSS 規則的時代了,就像你寫 CSS-in-JS 那樣。
沒什麼能阻止你在原子 CSS-in-JS 的框架上創建你本身的抽象 CSS 規則,Styled-system 就能在 CSS-in-JS 庫裏完成一些相似的事情。它基於一些約定創造出一些原子規則,在 emotion
中使用它試試:
import styled from '@emotion/styled';
import { typography, space, color } from 'styled-system';
const Box = styled('div')(typography, space, color);
複製代碼
等效於:
<Box
fontSize={4}
fontWeight="bold"
p={3}
mb={[4, 5]}
color="white"
bg="primary"
>
Hello
</Box>
複製代碼
甚至有可能在 JS 裏複用一些 Tailwind 的命名約定,若是你喜歡的話。
先看些 Tailwind 的代碼:
<div className="absolute inset-0 p-4 bg-blue-500" />
複製代碼
咱們在谷歌上隨便找一個方案,好比我剛剛發現 react-native-web-tailwindcss:
import { t } from 'react-native-tailwindcss';
<View style={[t.absolute, t.inset0, t.p4, t.bgBlue500]} />;
複製代碼
就生產力的角度而言,並無太大的不一樣。甚至能夠用 TS 來避免錯別字。
這就是我要說的關於原子 CSS-in-JS 全部內容。
我歷來沒有在任何大型生產部署中使用過原子 CSS、原子 CSS-in-JS 或 Tailwind。我可能在某些方面是錯的,請隨時糾正我。
我以爲在 React 生態系統中,原子 CSS-in-JS 是一個很是值得關注的趨勢,我但願你能從這篇文章中學到一些有用的東西。
感謝閱讀。
本文首發於公衆號「前端從進階到入院」,歡迎關注。
字節跳動招人啦,明年光是咱們組就新增了十幾個 HC,真的很缺人。北上廣深杭都有,隨時面試均可以,我會幫你作好全方位的內推服務!歡迎經過留言、公衆號聯繫到我諮詢 😁。