好家伙,這些寫 CSS 的新姿勢你還不知道?
現(xiàn)在大部分搞前端的應(yīng)該還是這樣寫 CSS 的:
- .mock {
 - margin: auto;
 - font-size: 16px;
 - // ...
 - }
 - <div class='mock'>mock</div>
 
以上代碼就是舉個例子,大部分情況應(yīng)該都是寫一個類,然后整一堆樣式進去。
但是這種方式寫多了以后,你應(yīng)該會感受到一些痛點,比如說:
- 取名困難,節(jié)點結(jié)構(gòu)一多,取名真的是個難事。當(dāng)然了,我們可以用一些規(guī)范或者選擇器的方式去規(guī)避一些取名問題。
 - 需要用 JS 控制樣式的時候又得多寫一個類,尤其交互多的場景。
 - 組件復(fù)用大家都懂,但是樣式復(fù)用少之又少,這樣就造成了冗余代碼變多。
 - 全局污染,這個其實現(xiàn)在挺多工具都能幫我們自動解決了。
 - 死代碼問題。JS 我們通過 tree shaking 的方式去除用不到的代碼減少文件體積,但是 CSS 該怎么去除?尤其當(dāng)項目變大以后,無用 CSS 代碼總會出現(xiàn)。
 - 樣式表的插入順序影響了 CSS 到底是如何生效的。
 - 等等,不一一說明了。其實對于筆者而言,第一二塊在開發(fā)中是最難受的兩個點,尤其是剛寫前端,需要做活動 / 產(chǎn)品頁的時候。
 
當(dāng)下,社區(qū)里有一些 CSS 方案,能夠解決以上一些痛點:
- Atom CSS
 - CSS-in-JS
 - 上述兩者的結(jié)合體
 
本文就來聊聊以上三種方案的優(yōu)缺點以及各自方案的代表作。
Atom CSS
首先來聊聊啥叫做 Atom CSS:意思是一個類只干一件事,比如說:
- .m-8 {
 - margin: 8px;
 - }
 
想象一下你按照這樣的思想搞出一大堆類似的類名,就能整出一個踐行 Atom CSS 方案的三方庫了,tailwindcss 就是這個方案里的佼佼者。其實 Atom CSS 很多人應(yīng)該早都用過了,柵格系統(tǒng)上就有它的身影,無非不清楚原來它就是 Atom CSS 罷了。
我們先來看看如果用 tailwindcss 的話,寫好樣式的 HTML 大概長啥樣:
上圖是人家官網(wǎng)上的,在這之前還有一段挺炫的動畫??雌饋砗孟裢Ψ奖愕模瑢懮弦欢杨惷湍艹鲎筮吅每吹臉邮搅?,省了很多寫樣式的時間,但是讀者們可以來想想這種方式它會有啥好處及弊端?
在說優(yōu)缺點之前,我們先來聊聊 Atom CSS 的歷史。其實它并不是一個新興產(chǎn)物,這玩意你往前推個十年就能看到它的討論。正所謂天道好輪回,蒼天饒過誰。Atom CSS 以前火過,而且是被噴火的,沉寂了幾年之后這幾年又被拿出來說了。
接下來我們以 tailwindcss 為例來聊聊 Atom CSS 方案的優(yōu)劣點。
優(yōu)劣點
如果你想在團隊內(nèi)部推廣這個產(chǎn)品,學(xué)習(xí)成本會是一個問題,畢竟需要大家都看得懂你這坨東西到底是啥意思,這算一個很明顯的缺陷。但是對于語法問題你還真的不用怎么擔(dān)心,tailwindcss 是有語法補全的工具鏈的,Webstorm 已經(jīng)內(nèi)置了,VSCode 需要大家自行裝個插件,所以噴寫 tailwindcss 語法麻煩的可以歇一歇。
樣式復(fù)用,就像寫組件一樣,這次我們是把樣式一個個抽離了出來,這樣帶來的一大好處是減少了 CSS 代碼文件體積。
原本傳統(tǒng)的寫法是定義一個類,然后寫上需要的樣式:
- .class1 {
 - font-size: 18px;
 - margin: 10px;
 - }
 - .class2 {
 - font-size: 16px;
 - color: red;
 - margin: 10px;
 - }
 
這種寫法是存在一部分樣式重復(fù)的,換成 Atom CSS 就能減少一部分代碼的冗余。
把 CSS 當(dāng)成組件來寫。大家乍一看 tailwindcss 官網(wǎng)肯定會覺得我在 HTML 里寫個樣式要敲那么多類是有病吧?
- <figure class="md:flex bg-gray-100 rounded-xl p-8 md:p-0">
 - <img class="w-32 h-32 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto" src="/sarah-dayan.jpg" alt="" width="384" height="512">
 - <div class="pt-6 md:p-8 text-center md:text-left space-y-4">
 - <blockquote>
 - <p class="text-lg font-semibold">
 - “Tailwind CSS is the only framework that I've seen scale
 - on large teams. It’s easy to customize, adapts to any design,
 - and the build size is tiny.”
 - </p>
 - </blockquote>
 - <figcaption class="font-medium">
 - <div class="text-cyan-600">
 - Sarah Dayan
 - </div>
 - <div class="text-gray-500">
 - Staff Engineer, Algolia
 - </div>
 - </figcaption>
 - </div>
 - </figure>
 
其實我們是可以利用 Atom CSS 一次只干一件事的特性,將這些類隨意組裝成我們想要的類,這樣就可以提供出來一個更上層的通用樣式來復(fù)用。
比如說項目中的按鈕都是存在通用的圓角、內(nèi)邊距、字體等,這樣我們就可以封裝出這樣一個類:
- .btn {
 - @apply p-8 rounded-xl font-semibold
 - }
 
效率工具。tailwindcss 用的好肯定是能提高寫布局的效率的,尤其對于需要做響應(yīng)式的頁面而言。當(dāng)然這東西其實也算是甲之蜜糖乙之砒霜,評價兩極分化很嚴重,有人認為提高了效率,也有人認為反而是增加了成本,或者說是脫褲子放屁。
提供了一整套規(guī)范化的設(shè)計模式,直接點說就是 tailwindcss 給你內(nèi)置好一套優(yōu)秀的設(shè)計主題了。但是這玩意對于規(guī)范的視覺團隊來說是個不小的福音,不規(guī)范的話就可能是火葬場了。下面我給大家舉個例子:
- // tailwind.config.js
 - const colors = require('tailwindcss/colors')
 - module.exports = {
 - theme: {
 - screens: {
 - sm: '480px',
 - md: '768px',
 - lg: '976px',
 - xl: '1440px',
 - },
 - colors: {
 - gray: colors.coolGray,
 - blue: colors.lightBlue,
 - red: colors.rose,
 - pink: colors.fuchsia,
 - },
 - fontFamily: {
 - sans: ['Graphik', 'sans-serif'],
 - serif: ['Merriweather', 'serif'],
 - },
 - extend: {
 - spacing: {
 - '128': '32rem',
 - '144': '36rem',
 - },
 - borderRadius: {
 - '4xl': '2rem',
 - }
 - }
 - }
 - }
 
以上是 tailwindcss 的主題配置文件,大家可以按照視覺的要求來做調(diào)整。比如說今天視覺覺得屏幕的 lg 尺寸應(yīng)該是 976px,過段時間又覺得需要改成 1000px。對于開發(fā)者而言我們只需要修改一行代碼就能全局生效了,很舒服。
但是假如說視覺原本定義的邊距規(guī)則如下:
- // tailwind.config.js
 - module.exports = {
 - theme: {
 - spacing: {
 - px: '1px',
 - 0: '0',
 - 0.5: '0.125rem',
 - 1: '0.25rem',
 - 1.5: '0.375rem',
 - 2: '0.5rem',
 - 2.5: '0.625rem',
 - 3: '0.75rem',
 - 3.5: '0.875rem',
 - 4: '1rem',
 - 5: '1.25rem',
 - 6: '1.5rem',
 - 7: '1.75rem',
 - 8: '2rem',
 - // ...
 - },
 - }
 - }
 
現(xiàn)在需要我們把 6 換成 1.6rem,但是這個規(guī)則只需要作用在某些組件上,此時我們需要如何修改樣式?新增一個 spacing 然后一個個去替換需要的地方么?
上述場景筆者認為還是不少見的,最起碼在我們公司內(nèi)部是存在這樣的問題。已經(jīng)定義了視覺規(guī)范并體現(xiàn)在內(nèi)部的組件庫上,但是在業(yè)務(wù)中還是有不少視覺會去動組件的基本樣式,這里改個邊距,那里改個顏色等等。原本組件庫是為了幫助開發(fā)者提效的,但是在這種場景下開發(fā)者反而會抱怨改動樣式極大提高了他們的成本,并且大部分情況下還不得不這樣做。
再說回傳統(tǒng) CSS 的問題,其實 tailwindcss 也解決了一部分,但是仍舊存在沒解決的點,比如說:
- 死代碼問題沒解決
 - 樣式表的插入順序依舊有影響
 
以上說了那么多,其實對于我們使用 tailwindcss 而言,有利也有弊。它肯定是存在很好用的場景的,比如說寫個人的產(chǎn)品頁,或者說業(yè)務(wù)中樣式變化不頻繁的場景中,但是如果說需要業(yè)務(wù)中全量切換到 tailwindcss 的話,筆者肯定是持保留態(tài)度的。
對于 Atom CSS 來說,大家應(yīng)該是不能否認它的優(yōu)點的,但是我們是否有辦法在盡可能避免它的缺點的情況下又獲得它的優(yōu)點呢?答案是有的,但是在講答案之前我想先來聊聊 CSS-in-JS。
CSS-in-JS
CSS-in-JS(下文以 CIJ 縮寫表示)核心就是在用 JS 寫 CSS,這同樣也是一個頗具爭議的技術(shù)方案。
在這個領(lǐng)域下有兩個庫比較流行,分別為:styled-components(下文以 sc 縮寫表示) 以及 Emotion。筆者目前已經(jīng)用了一年多的 sc 了,來粗略談?wù)勊膬?yōu)缺點。
我們先來了解下 sc 是怎么使用的。首先說下 sc 和 Emotion 的語法是趨于一致的,應(yīng)該是為了 API 層面的統(tǒng)一吧,甚至前者還依賴了后者的一些包,以下是 sc 的常用寫法:
- const Button = styled.a`
 - display: inline-block;
 - ${props => props.primary && css`
 - background: white;
 - color: black;
 - `}
 - `
 - render(
 - <div>
 - <Button
 - href="https://github.com/styled-components/styled-components"
 - target="_blank"
 - rel="noopener"
 - primary
 - >
 - GitHub
 - </Button>
 - <Button as={Link} href="/docs">
 - Documentation
 - </Button>
 - </div>
 - )
 
用法我們不多展開,有興趣的可以去官方看看,基本沒有學(xué)習(xí)成本的,主要是一些樣式組件上的使用。
另外 sc 并不是最終生成了內(nèi)聯(lián)樣式,而是幫我們插入了 style 標(biāo)簽。
優(yōu)劣點
筆者用了一年多的 sc,感覺這種方案對于 React 來說是很香的。并且解決了我很討厭的傳統(tǒng)寫 CSS 的一些點,所以關(guān)于優(yōu)劣點這段的講述會有點主觀。
首先 CSS-in-JS 這種方案不僅能讓我們完整使用到 CSS 的功能,而且還擴充了一些用法。比如說選擇器這塊,在 sc 中我們能通過選擇組件的方式來編寫樣式,如下代碼:
- const Button = styled.a`
 - ${Icon} {
 - color: green;
 - }
 - `
 
另外既然我們通過 JS 來管理 CSS 了,那么我們就可以充分享受 JS 帶來的工具鏈好處了。一旦項目中出現(xiàn)沒有使用到的樣式組件,那么 ESLint 就可以幫助我們找到那些死代碼并清除,這個功能對于大型項目來說還是能減少一部分代碼體積的。
除此之外,樣式污染、取名問題、自動添加前綴這些問題也很好的解決了。
除了以上這些,再來聊兩點不容易注意到的。
首先是動態(tài)切換主題。因為我們是通過 JS 來寫 CSS 了,那么我們就可以動態(tài)地控制樣式。如果你的項目有切換主題這種類似的大量動態(tài) CSS 的需求,那么這個方案會是一個不錯的選擇。
還有個點是按需加載。因為我們是通過 JS 寫的 CSS,現(xiàn)階段打包基本都走的 code split,那么就可以實現(xiàn) CSS 文件的按需加載,而不是傳統(tǒng)方式的一次性全部加載進來(當(dāng)然也是可以優(yōu)化的,只是沒那么方便)。
聊完了優(yōu)點我們再來說說缺點。
第一個缺點很明顯,有學(xué)習(xí)成本,當(dāng)然筆者覺得這個學(xué)習(xí)曲線還是平緩的。
運行時成本,sc 本身就有文件體積,加上還需要動態(tài)生成 CSS,那么這其中必定有性能上的損耗。項目越大影響的也會越大,如果你的項目對于性能有很高的要求,那么需要謹慎考慮使用。另外因為 CSS 動態(tài)生成,所以不能像傳統(tǒng) CSS 一樣緩存 CSS 文件了。
代碼復(fù)用性和傳統(tǒng)寫 CSS 的方式?jīng)]啥兩樣。
最后點是代碼耦合問題。會有人覺得在大型項目中將 CSS 及 JS 寫在一起會增加維護成本,并且也不符合 CSS 需要分離開來想法。
Atom CSS 加上 CSS-in-JS 的縫合怪
看了上文,如果你覺得兩種方案都挺好的話,可以了解下 twin.macro,這個庫(還有別的競品)把這兩種方案融合了起來。
- import 'twin.macro'
 - const Input = () => <input tw="border hover:border-black" />
 - const Input = tw.input`border hover:border-black`
 
這種方案之上其實還有更好玩的方式,能幫助我們盡量取其精華而去其糟粕。
自動生成 Atom CSS 的 CSS-in-JS 方案
假如說我不僅想用 CSS-in-JS,還想把 Atom CSS 也給整上,但是又不想記 / 寫一大堆類名,我這個想法能實現(xiàn)么?
答案是有的。利用運行時的方式把單個樣式抽離出來,最后實現(xiàn)雖然我們寫的是 CSS-in-JS,但是最終呈現(xiàn)的是 Atom CSS 的樣子。
以 styletron 舉個例子,開發(fā)時候的代碼長這樣:
- import { styled } from "styletron-react";
 - export default () => {
 - // Create a styled component by passing
 - // an element name and a style object
 - const Anchor = styled("a", {
 - fontSize: "20px",
 - color: "red"
 - });
 - return <Anchor href="/getting-started">Start!</Anchor>;
 - };
 
實際編譯出來的時候長這樣:
- <html>
 - <head>
 - <style>
 - .foo {
 - font-size: 20px;
 - }
 - .bar {
 - color: red;
 - }
 - </style>
 - </head>
 - <body>
 - <a href="/getting-started" class="foo bar">Start!</a>
 - </body>
 - </html>
 
這樣的方式就能很好地享受到兩種方案帶來的好處了。但是這類方案筆者找了些競品,覺得還沒有前兩者方案來的流行,大家了解一下即可。另外這種方式帶來的運行時成本應(yīng)該會更大,也許可以配套打包工具在本地先做一次預(yù)編譯(一個不成熟的想法,說錯勿噴)?
總結(jié)
說了那么多方案,可能讀者會有疑問,那么我到底該用啥?這里筆者說下自己的想法。
首先對于 sc 來說,筆者覺得很香,在項目中大范圍用起來未嘗不可,當(dāng)然我們還可以搭配著 Atom CSS 一起來寫通用樣式。
對于 Atom CSS,筆者個人認為不適合項目中大規(guī)模使用,起碼在我們公司內(nèi)部不會是一個好方案,畢竟視覺真的會來動某些通用樣式。
















 
 
 









 
 
 
 