好家伙,這些寫 CSS 的新姿勢你還不知道?
現(xiàn)在大部分搞前端的應(yīng)該還是這樣寫 CSS 的:
- .mock {
- margin: auto;
- font-size: 16px;
- // ...
- }
- <div class='mock'>mock</div>
以上代碼就是舉個(gè)例子,大部分情況應(yīng)該都是寫一個(gè)類,然后整一堆樣式進(jìn)去。
但是這種方式寫多了以后,你應(yīng)該會(huì)感受到一些痛點(diǎn),比如說:
- 取名困難,節(jié)點(diǎn)結(jié)構(gòu)一多,取名真的是個(gè)難事。當(dāng)然了,我們可以用一些規(guī)范或者選擇器的方式去規(guī)避一些取名問題。
- 需要用 JS 控制樣式的時(shí)候又得多寫一個(gè)類,尤其交互多的場景。
- 組件復(fù)用大家都懂,但是樣式復(fù)用少之又少,這樣就造成了冗余代碼變多。
- 全局污染,這個(gè)其實(shí)現(xiàn)在挺多工具都能幫我們自動(dòng)解決了。
- 死代碼問題。JS 我們通過 tree shaking 的方式去除用不到的代碼減少文件體積,但是 CSS 該怎么去除?尤其當(dāng)項(xiàng)目變大以后,無用 CSS 代碼總會(huì)出現(xiàn)。
- 樣式表的插入順序影響了 CSS 到底是如何生效的。
- 等等,不一一說明了。其實(shí)對(duì)于筆者而言,第一二塊在開發(fā)中是最難受的兩個(gè)點(diǎn),尤其是剛寫前端,需要做活動(dòng) / 產(chǎn)品頁的時(shí)候。
當(dāng)下,社區(qū)里有一些 CSS 方案,能夠解決以上一些痛點(diǎn):
- Atom CSS
- CSS-in-JS
- 上述兩者的結(jié)合體
本文就來聊聊以上三種方案的優(yōu)缺點(diǎn)以及各自方案的代表作。
Atom CSS
首先來聊聊啥叫做 Atom CSS:意思是一個(gè)類只干一件事,比如說:
- .m-8 {
- margin: 8px;
- }
想象一下你按照這樣的思想搞出一大堆類似的類名,就能整出一個(gè)踐行 Atom CSS 方案的三方庫了,tailwindcss 就是這個(gè)方案里的佼佼者。其實(shí) Atom CSS 很多人應(yīng)該早都用過了,柵格系統(tǒng)上就有它的身影,無非不清楚原來它就是 Atom CSS 罷了。
我們先來看看如果用 tailwindcss 的話,寫好樣式的 HTML 大概長啥樣:
上圖是人家官網(wǎng)上的,在這之前還有一段挺炫的動(dòng)畫??雌饋砗孟裢Ψ奖愕?,寫上一堆類名就能出左邊好看的樣式了,省了很多寫樣式的時(shí)間,但是讀者們可以來想想這種方式它會(huì)有啥好處及弊端?
在說優(yōu)缺點(diǎn)之前,我們先來聊聊 Atom CSS 的歷史。其實(shí)它并不是一個(gè)新興產(chǎn)物,這玩意你往前推個(gè)十年就能看到它的討論。正所謂天道好輪回,蒼天饒過誰。Atom CSS 以前火過,而且是被噴火的,沉寂了幾年之后這幾年又被拿出來說了。
接下來我們以 tailwindcss 為例來聊聊 Atom CSS 方案的優(yōu)劣點(diǎn)。
優(yōu)劣點(diǎn)
如果你想在團(tuán)隊(duì)內(nèi)部推廣這個(gè)產(chǎn)品,學(xué)習(xí)成本會(huì)是一個(gè)問題,畢竟需要大家都看得懂你這坨東西到底是啥意思,這算一個(gè)很明顯的缺陷。但是對(duì)于語法問題你還真的不用怎么擔(dān)心,tailwindcss 是有語法補(bǔ)全的工具鏈的,Webstorm 已經(jīng)內(nèi)置了,VSCode 需要大家自行裝個(gè)插件,所以噴寫 tailwindcss 語法麻煩的可以歇一歇。
樣式復(fù)用,就像寫組件一樣,這次我們是把樣式一個(gè)個(gè)抽離了出來,這樣帶來的一大好處是減少了 CSS 代碼文件體積。
原本傳統(tǒng)的寫法是定義一個(gè)類,然后寫上需要的樣式:
- .class1 {
- font-size: 18px;
- margin: 10px;
- }
- .class2 {
- font-size: 16px;
- color: red;
- margin: 10px;
- }
這種寫法是存在一部分樣式重復(fù)的,換成 Atom CSS 就能減少一部分代碼的冗余。
把 CSS 當(dāng)成組件來寫。大家乍一看 tailwindcss 官網(wǎng)肯定會(huì)覺得我在 HTML 里寫個(gè)樣式要敲那么多類是有病吧?
- <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>
其實(shí)我們是可以利用 Atom CSS 一次只干一件事的特性,將這些類隨意組裝成我們想要的類,這樣就可以提供出來一個(gè)更上層的通用樣式來復(fù)用。
比如說項(xiàng)目中的按鈕都是存在通用的圓角、內(nèi)邊距、字體等,這樣我們就可以封裝出這樣一個(gè)類:
- .btn {
- @apply p-8 rounded-xl font-semibold
- }
效率工具。tailwindcss 用的好肯定是能提高寫布局的效率的,尤其對(duì)于需要做響應(yīng)式的頁面而言。當(dāng)然這東西其實(shí)也算是甲之蜜糖乙之砒霜,評(píng)價(jià)兩極分化很嚴(yán)重,有人認(rèn)為提高了效率,也有人認(rèn)為反而是增加了成本,或者說是脫褲子放屁。
提供了一整套規(guī)范化的設(shè)計(jì)模式,直接點(diǎn)說就是 tailwindcss 給你內(nèi)置好一套優(yōu)秀的設(shè)計(jì)主題了。但是這玩意對(duì)于規(guī)范的視覺團(tuán)隊(duì)來說是個(gè)不小的福音,不規(guī)范的話就可能是火葬場了。下面我給大家舉個(gè)例子:
- // 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,過段時(shí)間又覺得需要改成 1000px。對(duì)于開發(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,但是這個(gè)規(guī)則只需要作用在某些組件上,此時(shí)我們需要如何修改樣式?新增一個(gè) spacing 然后一個(gè)個(gè)去替換需要的地方么?
上述場景筆者認(rèn)為還是不少見的,最起碼在我們公司內(nèi)部是存在這樣的問題。已經(jīng)定義了視覺規(guī)范并體現(xiàn)在內(nèi)部的組件庫上,但是在業(yè)務(wù)中還是有不少視覺會(huì)去動(dòng)組件的基本樣式,這里改個(gè)邊距,那里改個(gè)顏色等等。原本組件庫是為了幫助開發(fā)者提效的,但是在這種場景下開發(fā)者反而會(huì)抱怨改動(dòng)樣式極大提高了他們的成本,并且大部分情況下還不得不這樣做。
再說回傳統(tǒng) CSS 的問題,其實(shí) tailwindcss 也解決了一部分,但是仍舊存在沒解決的點(diǎn),比如說:
- 死代碼問題沒解決
- 樣式表的插入順序依舊有影響
以上說了那么多,其實(shí)對(duì)于我們使用 tailwindcss 而言,有利也有弊。它肯定是存在很好用的場景的,比如說寫個(gè)人的產(chǎn)品頁,或者說業(yè)務(wù)中樣式變化不頻繁的場景中,但是如果說需要業(yè)務(wù)中全量切換到 tailwindcss 的話,筆者肯定是持保留態(tài)度的。
對(duì)于 Atom CSS 來說,大家應(yīng)該是不能否認(rèn)它的優(yōu)點(diǎn)的,但是我們是否有辦法在盡可能避免它的缺點(diǎn)的情況下又獲得它的優(yōu)點(diǎn)呢?答案是有的,但是在講答案之前我想先來聊聊 CSS-in-JS。
CSS-in-JS
CSS-in-JS(下文以 CIJ 縮寫表示)核心就是在用 JS 寫 CSS,這同樣也是一個(gè)頗具爭議的技術(shù)方案。
在這個(gè)領(lǐng)域下有兩個(gè)庫比較流行,分別為:styled-components(下文以 sc 縮寫表示) 以及 Emotion。筆者目前已經(jīng)用了一年多的 sc 了,來粗略談?wù)勊膬?yōu)缺點(diǎn)。
我們先來了解下 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)劣點(diǎn)
筆者用了一年多的 sc,感覺這種方案對(duì)于 React 來說是很香的。并且解決了我很討厭的傳統(tǒng)寫 CSS 的一些點(diǎn),所以關(guān)于優(yōu)劣點(diǎn)這段的講述會(huì)有點(diǎn)主觀。
首先 CSS-in-JS 這種方案不僅能讓我們完整使用到 CSS 的功能,而且還擴(kuò)充了一些用法。比如說選擇器這塊,在 sc 中我們能通過選擇組件的方式來編寫樣式,如下代碼:
- const Button = styled.a`
- ${Icon} {
- color: green;
- }
- `
另外既然我們通過 JS 來管理 CSS 了,那么我們就可以充分享受 JS 帶來的工具鏈好處了。一旦項(xiàng)目中出現(xiàn)沒有使用到的樣式組件,那么 ESLint 就可以幫助我們找到那些死代碼并清除,這個(gè)功能對(duì)于大型項(xiàng)目來說還是能減少一部分代碼體積的。
除此之外,樣式污染、取名問題、自動(dòng)添加前綴這些問題也很好的解決了。
除了以上這些,再來聊兩點(diǎn)不容易注意到的。
首先是動(dòng)態(tài)切換主題。因?yàn)槲覀兪峭ㄟ^ JS 來寫 CSS 了,那么我們就可以動(dòng)態(tài)地控制樣式。如果你的項(xiàng)目有切換主題這種類似的大量動(dòng)態(tài) CSS 的需求,那么這個(gè)方案會(huì)是一個(gè)不錯(cuò)的選擇。
還有個(gè)點(diǎn)是按需加載。因?yàn)槲覀兪峭ㄟ^ JS 寫的 CSS,現(xiàn)階段打包基本都走的 code split,那么就可以實(shí)現(xiàn) CSS 文件的按需加載,而不是傳統(tǒng)方式的一次性全部加載進(jìn)來(當(dāng)然也是可以優(yōu)化的,只是沒那么方便)。
聊完了優(yōu)點(diǎn)我們?cè)賮碚f說缺點(diǎn)。
第一個(gè)缺點(diǎn)很明顯,有學(xué)習(xí)成本,當(dāng)然筆者覺得這個(gè)學(xué)習(xí)曲線還是平緩的。
運(yùn)行時(shí)成本,sc 本身就有文件體積,加上還需要?jiǎng)討B(tài)生成 CSS,那么這其中必定有性能上的損耗。項(xiàng)目越大影響的也會(huì)越大,如果你的項(xiàng)目對(duì)于性能有很高的要求,那么需要謹(jǐn)慎考慮使用。另外因?yàn)?CSS 動(dòng)態(tài)生成,所以不能像傳統(tǒng) CSS 一樣緩存 CSS 文件了。
代碼復(fù)用性和傳統(tǒng)寫 CSS 的方式?jīng)]啥兩樣。
最后點(diǎn)是代碼耦合問題。會(huì)有人覺得在大型項(xiàng)目中將 CSS 及 JS 寫在一起會(huì)增加維護(hù)成本,并且也不符合 CSS 需要分離開來想法。
Atom CSS 加上 CSS-in-JS 的縫合怪
看了上文,如果你覺得兩種方案都挺好的話,可以了解下 twin.macro,這個(gè)庫(還有別的競品)把這兩種方案融合了起來。
- import 'twin.macro'
- const Input = () => <input tw="border hover:border-black" />
- const Input = tw.input`border hover:border-black`
這種方案之上其實(shí)還有更好玩的方式,能幫助我們盡量取其精華而去其糟粕。
自動(dòng)生成 Atom CSS 的 CSS-in-JS 方案
假如說我不僅想用 CSS-in-JS,還想把 Atom CSS 也給整上,但是又不想記 / 寫一大堆類名,我這個(gè)想法能實(shí)現(xiàn)么?
答案是有的。利用運(yùn)行時(shí)的方式把單個(gè)樣式抽離出來,最后實(shí)現(xiàn)雖然我們寫的是 CSS-in-JS,但是最終呈現(xiàn)的是 Atom CSS 的樣子。
以 styletron 舉個(gè)例子,開發(fā)時(shí)候的代碼長這樣:
- 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>;
- };
實(shí)際編譯出來的時(shí)候長這樣:
- <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ùn)行時(shí)成本應(yīng)該會(huì)更大,也許可以配套打包工具在本地先做一次預(yù)編譯(一個(gè)不成熟的想法,說錯(cuò)勿噴)?
總結(jié)
說了那么多方案,可能讀者會(huì)有疑問,那么我到底該用啥?這里筆者說下自己的想法。
首先對(duì)于 sc 來說,筆者覺得很香,在項(xiàng)目中大范圍用起來未嘗不可,當(dāng)然我們還可以搭配著 Atom CSS 一起來寫通用樣式。
對(duì)于 Atom CSS,筆者個(gè)人認(rèn)為不適合項(xiàng)目中大規(guī)模使用,起碼在我們公司內(nèi)部不會(huì)是一個(gè)好方案,畢竟視覺真的會(huì)來動(dòng)某些通用樣式。