前言
關注點分離(separation of concerns)原則多年來大行其道,實踐中一般將 HTML
、CSS
、JavaScript
分開編寫維護,早期框架 angularjs
即是如此,直到 React
爭議中問世,引領關注點混合趨勢,驅使開發者重新審視 CSS
工程化發展。
尷尬的CSS
相對於 JavaScript
的突飛猛進,CSS
的發展緩慢,相對止步不前。隨著前端職能擴大化成為常態,前端工程化日趨成熟,CSS
先天缺陷愈發明顯:
- 全域性作用域
- 缺乏高階程式設計特性
- 程式碼冗餘
- 極限壓縮
- 依賴管理
最大的缺陷 來自於全域性作用域,class name
全域性生效,多人協作中的風格不一致,隨時可能引發蝴蝶效應。為規避多人協作的風格衝突,社群提出 OOCSS
,BEM
等方法論,但實踐中完全取決於團隊執行力度,筆者也曾苦惱於合理的命名,為避免衝突,導致類名冗長,無聊且痛苦。
缺乏高階程式設計特性 影響同樣深遠,社群發展的前處理器能夠有效緩解,sass
,less
,stylus
殊途同歸,postcss
異軍突起,基本實現變數、巢狀、變數、混合、擴充套件和邏輯等。隨著 CSS
規範逐步推進,高階程式設計特性完全可期。筆者大膽斷言,前端工程化的推進,已經基本解決 CSS高階程式設計特性缺乏 的問題。
程式碼冗餘,極限壓縮對開發的影響相對很小,經典的 bootstrap
就包含大量的冗餘程式碼,但絲毫不影響其流行程度。
目前難以解決的是依賴管理,NPM
已經成為事實上的 JavaScript
包管理工具,而 CSS
始終沒有發展出可用的管理模式,sass
的淺嘗輒止,例如 bootstrap-sass
, Bourbon
等,顯然無法滿足需求。隨著 React
引領的關注點混合,以元件為核心的開發模式,有效規避了 CSS
缺乏依賴管理的缺陷,筆者認為,依賴管理弊端完全可控,未來的發展,留給未來述說。
新銳的元件化
前端發展日新月異,React
在眾人爭議中進入視野,典型的 React
元件同時包含結構、樣式、行為,示例如下:
/**
* @description - lite component
* @author - huang.jian <hjj491229492@hotmail.com>
*/
export class Counter extends Component {
constructor(props) {
super(props);
this.state = {
timestamp: Date.now()
};
}
render() {
return (
<Card title="React Timestamp">
<Alert message={`React Timestamp: ${this.state.timestamp}`} type="success"/>
<Alert message={`React Timestamp: ${this.state.timestamp}`} type="info"/>
<Alert message={`React Timestamp: ${this.state.timestamp}`} type="warning"/>
</Card>
);
}
}
複製程式碼
前端應用由元件聚合而成,元件層面對 CSS
進行抽象,從而解決大型應用的 CSS
維護難題。社群出現的 CSS IN JS
解決方案,目前看來就是可行解決方案,其本質在於通過 JavaScript
來宣告,維護樣式,以 styled-components
舉例:
const Button = styled.button`
border-radius: 3px;
padding: 0.25em 1em;
color: palevioletred;
border: 2px solid palevioletred;
`;
function Buttons() {
return (
<Button>Normal Button</Button>
<Button primary>Primary Button</Button>
);
}
複製程式碼
樣式寄生元件之中,元件掛載時,動態插入樣式,實現按需載入,動態生成類名,隔離作用域。另外一種思路,通過 style
屬性傳入內嵌樣式,完全規避選擇器全域性作用域的問題。
// 官方示例有刪減
var Radium = require('radium');
var React = require('react');
var color = require('color');
// You can create your style objects dynamically or share them for
// every instance of the component.
var styles = {
base: {
color: '#fff',
},
primary: {
background: '#0074D9'
},
warning: {
background: '#FF4136'
}
};
@Radium
class Button extends React.Component {
render() {
return (
<button
style={[
styles.base,
styles[this.props.kind]
]}>
{this.props.children}
</button>
);
}
}
複製程式碼
面向元件開發,為樣式管理提供更多的可能性,完全使用 JavaScript
抽象,管理,維護樣式,略顯激進,但也不失為一種解決方案。
客觀的分析
目前主流的 CSS IN JS
方案與傳統的方式對比如下:
優勢:
- 隔離作用域 -- 樣式生效通過內嵌,或者生成獨一無二的類名,避免出現選擇器衝突;
- 高階程式設計特性 -- 充分利用 JavaScript 的能力增強對樣式的控制;
- 樣式按需掛載 -- 頁面需要的樣式才會載入,有效避免樣式冗餘;
- 依賴管理 -- 寄生於元件,利用現存的
NPM
生態進行包管理; - 動態樣式 -- 能夠更加簡單,直接的修改樣式
劣勢:
- 無法複用現有生態,特性完全依賴於庫的實現;
- 編輯器程式碼補全,語法檢查,語法高亮等需要外掛支援;
- 偽類選擇器(
disabled、:before、:nth-child
)支援詭異; - 樣式屬性駱駝式命名;
獨闢蹊徑
筆者並不完全認同 CSS IN JS
的理念,也不反對將其應用於生產專案。CSS
中最嚴重的問題,不通過 CSS-in-JS
也能
有其他解決方案,也就是筆者當前使用的 CSS Module
方案。通過工程化的方式,將選擇器編譯為獨一無二的類名,使用 JavaScript
管理選擇器與元素的關聯,僅此而已。
// Header.jsx
import style from './Header.css'
// { header: 'Header__header--3kSIq_0' }
export default function Header() {
return (<div className={style.header}>Header!!!</div>);
}
複製程式碼
優勢:
- 隔離作用域 -- 類名編譯生成,有效避免選擇器衝突;
- 樣式按需載入 -- 利用
tree-shaking
機制,僅保留存在引用的選擇器,有效避免樣式冗餘; - 依賴管理 -- 關聯元件,利用現存的
NPM
機制進行包管理; - 充分利用現有生態 -- 編輯器高亮,自動補全,
sass
,postcss
高階程式設計特性;
劣勢:
- 欠缺動態樣式特性 -- 無法充分利用 JavaScript 的能力增強對樣式的控制;
主觀的感悟
本文未涉及的 單檔案元件 也是可行方案之一,目前 Vue
,Angular
等框架採用。筆者始終認為,與其創造更多抽象的技術讓前端學習曲線更加陡峭,不如通過工程化的手段來修復存在的缺陷,理念上求同存異。面對各種技術方案,適合實際專案的方案才是最好的方案,選用前處理器 PostCSS
,BEM
,亦或動態編譯,都需要結合業務場景、團隊習慣等因素決策。
關注公眾號,獲取動態,支援作者。