Tipping Point Front-End Case Study and Practice: CSS

這次分享會從 CSS 歷史開始談起,接著會來看過去我們在使用元件庫上遇到的問題,以及怎麼使用 Styled System 來解決這些問題。最後會用 todo list 當成範例,並且利用趨勢自己開發的 Tonic Styled UI 來做改寫。

點這裡下載投影片 Part 1投影片 Part 2

CSS 的原罪

CSS 的原罪就是 CSS 只有全域而沒有區域的概念,也就是說,只要當前頁面的 DOM 結構符合載入樣式的規則,就會被套用這個樣式。這會產生兩個問題 - 命名衝突(name collision)和重用性(reusability)的問題。

命名衝突(Name Collisions)

第一個是命名衝突的問題,命名衝突是指因 CSS 樣式規則同名所造成樣式覆蓋的問題。

如這張圖所示,這裡分別有兩個元件 slideshow(左)和 tabs(右),它們各自擁有 .list.list .item 的樣式,若再載入了一個同樣由 .list.list .item 所撰寫或權重更高的樣式規則,就會把這個樣式套用上去,原先屬於這兩個元件的樣式就被覆蓋掉了,這就是命名衝突所造成的影響。

命名衝突

重用性(Reusability)

第二個是重用性的問題,由於 CSS 不是程式語言,它只是描述樣式的方法,因此在撰寫上很鬆散,沒有模組的概念,難以重用。若無法重用,就會造成程式碼毫無限制的成長,終至難以擴充和維護。


因此,一直以來,CSS 不管是撰寫模式還是開發工具,主要都是解決「命名衝突」和「重用性」這兩個問題。


CSS Methodologies

在撰寫模式方面,OOCSS、BEM、SMACSS 這些都是撰寫 CSS 的方法,讓前端工程師能試圖將 CSS 樣式規則切分成獨立模組來開發,而不是撰寫一大堆不可分割的程式碼。

這裡以 BEM 為例,BEM 將頁面元件分為三種類型

以下圖為例

BEM

BEM 幫我們解決了重用性的問題,但是還是沒有辦法解決命名衝突的問題。

基本上 OOCSS、BEM、SMACSS 主要都是利用命名規則的方式,讓樣式規則更模組化、更好重用;雖然命名規則能稍微避免一些命名衝突所造成的樣式覆蓋的問題,但仍無法完全解決,所以每次在寫樣式,前端工程師都不得不先將整個專案搜尋一下看看有沒有可以重用的,或是在當前引用的 css 檔案找找有沒有同名的樣式規則,避免覆蓋。

註:若想看這些方法論的更詳細內容,可參考這裡

Preprocessor / Postprocessor

在工具方面,預處理器(Preprocessor)和後處理器(Postprocessor),例如:LESS、SASS、SCSS、PostCSS,讓開發者可以把 CSS 當成程式語言來撰寫,像是有變數、巢狀、函式等,雖然能適度地解決命名衝突和重用性的問題,但都不算是從根本解決。

下面的例子是 LESS 用 mixing 的方式傳入變數 scope 來產生屬於這個模組的專屬樣式。也就是說,為了解決全域的問題,增加了區域的識別方式。

.search-box-mixin(@scope) {
  .@{scope}-search-box {
    .search-input {
      border: 1px solid @gray;
    }
    .tooltip {
      margin: 8px 0 0 1px;
    }
  }
}

傳入模組名稱「page」。

.search-box-mixin('page');

屬於這個模組「page」的專屬的 CSS。

.page-search-box .search-input {
  border: 1px solid #ddd;
}

.page-search-box .tooltip {
  margin: 8px 0 0 1px;
}

因此雖然適當命名可以解決命名衝突和重用性的問題,但只要是人來處理都可能產生先前提到的問題,像是有人取了重複名稱的模組。


以上就是簡單介紹 CSS 的主要困境「命名衝突」和「重用性」的問題與歷史上所用過的各種解法。


元件化的時代

回顧完過去,那我們來看現在的狀況。

現代的網頁都是以元件為單位來建立的。在以元件為基礎的時代,我們撰寫 CSS 的方式,也跟過去將樣式放在獨立的檔案有所不同,像是現在流行使用 CSS in JS,就是將該元件的樣式寫在元件的 js 裡面。

CSS Modules

CSS Modules 想做的是企圖把樣式鎖在特定的元件裡面。CSS Modules 仍是將樣式寫在樣式檔案裡面,並且透過 Webpack 設定 css-loader,針對引用進來的 class name 轉成 hash,成為唯一的名稱,這樣就能將 CSS 樣式規則限制在特定元件底下,完全避免因命名衝突所造成的樣式覆蓋的問題。

CSS Modules

CSS in JS

既然想把樣式限制在元件裡面,那直接把樣式寫在元件裡面就變得很合理,將樣式寫在元件裡面,就是 CSS in JS。

用 CSS in JS 這樣的概念開發的函式庫很多,這裡就以 Styled Components 為例。

下面的例子是利用 Styled Component 建立一個元件,樣式就寫在裡面。

import styled from 'styled-components';

const Button = styled.button`
  margin: 0 10px;
  padding: 10px;
  background: #fefefe;
  border-radius: 3px;
  border: 1px solid #ccc;
  color: #525252;
`;

使用 CSS in JS 的好處是


但是,我們還有些問題尚未解決,像是…

Styled System

這時候我們就可以用 Styled System 來做些改進了。Styled System 是一個搜集了許多 utility function 的 library,主要用來幫我們處理樣式,以下依照它的優點來看一些範例,了解到底要怎麼用它來改進我們的元件庫。

我們先來看 Styled System 到底有什麼優點?它可以讓我們有更精簡的程式碼、元件屬性命名能一致、容易客製化樣式、對於行動裝置的樣式有很好的支援度。

更精簡的程式碼

Styled System 第一個優點是能讓我們能更精簡程式碼,這是因為 Styled System 提供許多便利的 utility function 來幫助我們用更簡易的方法來撰寫樣式。

這是關於 Styled System 的 utility function 的例子,這裡有一個 Box 元件,它設定字體顏色為黑色、背景為蕃茄紅色。

utility function

幾乎所有 CSS-in-JS 函式庫在建立 styled component 時,都可接受函式(function)作為參數、並代入 props 來動態決定樣式,styled-components 也是。如下,color 與 background 的值是 props 傳入的,我們會在 styled component 單獨取出值來一個一個做對應。

const Box = styled.div`
  margin: 15px 0;
  padding: 15px;
  color: ${(props) => props.color};
  background: ${(props) => props.bg};
  border-radius: 10px;
`;

這樣很麻煩,所以我們用一個 getStyles function 統一來做這件事情,看起來稍微乾淨一點。

const getStyles = ({ color, bg }) => ({
  color,
  background: bg,
});

const Box = styled.div`
  ${getColor};
  margin: 15px 0;
  padding: 15px;
  border-radius: 10px;
`;

改用 Styled System 提供 color 這個 utility function,可達到相同的功能,可以想像 color utility function 挖了一個更大的洞一起填入 color 和 background 並幫我們做了一些繁瑣的 mapping 工作,對開發來說就便利許多。

import { color } from 'styled-system';

const Box = styled.div`
  ${color}
  margin: 15px 0;
  padding: 15px;
  border-radius: 10px;
`;

Styled System 針對不同特性的樣式而有許多 utility function 可用,可到它的官網查詢。

由於 Styled System 提供許多便利的 utility function 來幫助我們用更簡易的語法的撰寫樣式,而不用自己刻很多東西,讓我們的程式碼更簡潔,開發更便利。

元件屬性命名能一致

第二個優點是元件屬性命名能一致,由於元件的開發是不同人不同時間撰寫的,屬性命名風格難以統一,這在後續維護上很令人困惑。雖然這可以透過文件或工具來規範,還有其他方法嗎?

元件屬性命名能一致

舉例來說,這裡有兩個元件 Button 和 Label,它們都需要設定字體的顏色,可能由於開發人員或時間的不同,而有 color 和 fontColor 兩種,props 的名稱不同卻是要做一樣的事情,這很讓人困惑。由於 Styled System 提供了 API 來做樣式的對應設定,就可以強迫開發人員一定會用一樣的屬性名稱,這裡就是統一用 color 來定義字體顏色。

易於客製化樣式

第三個優點是 Styled System 能讓我們很容易地來客製化樣式,像是主題樣式和個別元件樣式的設定。

主題樣式

第一個要來看主題樣式。我們可以定義一個物件檔,裡面放全站主題樣式的設定。

const theme = {
  color: {
    white: '#fefefe',
  },
  bg: {
    tomato: 'tomato',
  },
};

將這個物件傳給 ThemeProvider,ThemeProvider 會利用 React Context 來傳遞樣式的設定到後續所有的元件,就可以讓所有的元件取用這個主題物件所定義的設定,不用一個一個元件設定。

主題樣式

上圖右是我們先準備好的 theme object,定義了背景色 bg 和字體的顏色 color,上圖左是元件,當元件設定的 color 或 bg 可在 theme 物件找到時,就會自動 mapping 來使用,這樣就能達到定義主題樣式的目的。

個別元件樣式

主題物件 theme 也可以定義元件單獨的樣式,只要在 variant 指定查找的 key 即可。

Variants

單一元件也可以利用 Variants 個別設定。如下,為元件 Box 定義了兩種類型 primary 和 secondary。

import { variant } from 'styled-system';

const Box = styled('div')(
  variant({
    variants: {
      primary: { color: 'black', bg: 'tomato' },
      secondary: { color: 'black', bg: 'yellow' },
    },
  }),
);

元件在使用時,只要利用 variant 去指定要哪一種就可以了,其中 color 和 bg 的值也是去查找 theme object 的設定。

Variants

Mobile First

第四個優點,是 Styled System 對於行動裝置有很好的支援度。

Styled System 提供簡易的 array syntax 語法來針對不同 breakpoint 設定各自的樣式。 以下是一般傳統的寫法,針對每個 breakpoint 寫各自的樣式。

.thing {
  font-size: 16px;
  width: 100%;
}

@media screen and (min-width: 40em) {
  font-size: 20px;
  width: 50%;
}

@media screen and (min-width: 52em) {
  font-size: 24px;
}

以下是 Styled System 的寫法,預設的 breakpoint 是 40em、52em、64em,在設定好 breakpoint 後,只要傳入相對應的數值即可,好處是少寫一些程式碼,也清楚明瞭。

<Thing fontSize={[16, 20, 24]} width={[1, 1 / 2]} />

以上就是 Styled System 的優點:它可以讓我們有更精簡的程式碼、屬性命名能一致、容易客製化樣式、對於行動裝置的樣式有很好的支援度。

從此以後,寫 css 都變簡單了呢!

Tonic Styled UI

來介紹趨勢自己開發的 Tonic Styled UI。

官網看這裡,從「Getting Started」開始,做基本的安裝和樣式的設定,接著就可以來使用它所提供的元件。

Styled Todo List with Tonic Styled UI

廢話不多說,從實際的例子來看吧。

這裡有一個範例TodoList_before.js 檔案是修改前,TodoList_after.js 是用 Tonic Styled UI 重構後。

試試看怎麼從樸實無華的原生元件重構為利用 Tonic Styled UI 的漂亮畫面吧。

如何利用 Tonic Styled UI 重構現有程式碼?

如何增加更多新的功能?

小試身手:來修個 bug 吧!

這裡有兩個臭蟲,快來修理看看!


styled-system styled-components Tonic Styled UI CSS Modules CSS in JS BEM css Modern Web Loading Performance Responsive Web Design 響應式網頁 加載效能 趨勢科技 Trend Micro sharing

Summer Tang
Sponsored
Summer Tang 一對一諮詢
Summer|TSMC 主任工程師|擁有 15 年資深 Web 架構經驗,職涯歷經跨國頂尖企業與新創。專精於高流量企業級應用開發,核心技術涵蓋微前端、效能優化、測試自動化與 SEO。曾出版多本技術著作,並多次擔任 MOPCON、WebConf 等大型技術年會講者。
了解更多 →