把問題的結論放在開頭,關于我的故事,感興趣的同學或者覺得會對自己有幫助的同學慢慢欣賞吧,這樣可以節省另外部分同學的時間。
1. css-modules是什么?
css-modules是將css賦予作用域概念的一種樣式模塊化解決方案。通過給樣式名加hash字符串后綴的方式,實現特定作用域語境中的樣式編譯后的樣式在全局唯一。
2. css-modules解決了什么問題?
當項目經由多位技術背景和水平參差不齊的開發者接手過后,項目的樣式已經不能乖乖的被后面的同學搞了,不斷的重構重構重構...... 嗯,是時候要有人拿小皮鞭來規范開發秩序了。css-modules的出現解決了全局樣式污染,龐大的頁面dom節點取名字的問題。
3. css-modules的科學實踐
組件容器內樣式作用域弱約束,頁面區域間作用域強隔離。
例如頁面結構為 header、main、aside、footer 這四個區域。header和aside共同使用了Navbar組件,Navbar的組件是復用了,樣式一致,但是在header和aside中要有兩種視覺效果。這時候把header和aside可以分別看做Navbar的兩個實例容器,header和aside是用css-module管理的local作用域,在他們的local作用域中聲明一個global作用域,通過.header :global .nav-bar{...}
的方式覆寫組件樣式,實現同一組件在同一頁面不同區域的樣式自定義。
4. 對css-modules出現背景所做的反思
css對于專業前端來說是很熟悉的,通過命名前綴,結構化命名來描述頁面結構。但這些都是人為經驗去做的代碼管理,在團隊開發資源緊張的情況下,很多不熟悉css的后端同學也需要盡快完成前端的任務,導致大量的樣式沖突和冗余。于是乎,標準化組件給大家更少的選擇,更少的關注css...
當然以上是出于許許多多后端、客戶端發者對web前端的理解基礎上給出的解決方案。語言之間的思想是會互相影響的,就那這個模塊化的方案結合es6來說,我感覺就很像python了。
下面是關于我對css-modules使用經歷的小故事:
不久前換了工作,開始使用React來開發web。早先就聽聞過React的大名,之前的工作是團隊中就我一只前端,考慮到效率問題,工作中一直在用Angular 1.X 來霸道的進行開發,功能大而全,即使是一個人來同時做前后端,也可以很快的開發出可維護性的系統。那么現在開始用了React,初次的開發體驗我選擇了"ant-design"。
根據ant-design的快速上手教程,我搭建起來一個初始化的項目結構。但是當我需要按照設計圖去還原視覺效果的時候,我懵逼了。jsx的語法,加上模塊化的React組件,在開發界面功能的時候很方便,有種類似客戶端的控件引入方式。不過在我需要按照視覺設計圖去覆寫組件樣式的時候,我發現竟然沒有對應的css樣式!
經過調試發現,項目結構中引入了css-modules,在瀏覽器的開發者工具中會看到,頁面中節點的className被加上了hash字符串,這也就意味著css有了作用域的概念。后來查css-modules的文檔,如果是全局作用域的樣式需要在樣式前面包一層 :global,代碼如下:
:global {
html, body, #root {
height: 100%;
}
body { background: #fafafa; }
}
在沒有:global包裹的樣式,編譯后的結果是一下這種:
MainLayout.less
.main{...}
index.html
<div id="root">
<div class="main"></div>
</div>
編譯后的index.html
<div id="root">
<div class="main___1gjAI"></div>
</div>
MainLayout.jsx
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { DatePicker, message } from 'antd';
import styles from './MainLayout.less';
ReactDOM.render(
<div className={styles.main}>
<DatePicker onChange={value => this.handleChange(value)} />
</div>,
document.getElementById('root')
);
這里想要讓css樣式生效,必須在對應的節點使用 "module.style"(模
塊.樣式屬性)的方式綁定樣式。這里就沒辦法通過外部容器的選擇器來定位組件節點了。因為組件是在自定義渲染部分引入使用,組件節點是自動渲染的,如果組件沒有開放子節點的className字段,是無法修改組件樣式的。
不過,如果在組件沒有使用css-modules的情況下,我們可以通過在頁面節點-組件容器的css作用域來創建一個組件容器的全局作用域(該作用域存在于頁面節點的local作用域內,頁面其他區域的css作用域是隔離的),代碼如下:
.main{
:global{
.ant-calendar-picker{
/*這里可以覆寫 DatePicker組件的樣式了*/
}
}
}
在經過一陣驚訝和反思后,結合我之前的工作經歷不禁開始贊嘆css-modules的牛逼。
說他牛逼并不是因為他在技術上的實現有多么復雜,而是他可以讓項目的不確定性大大降低,代碼的可維護性得以提升。
以前也跟朋友吐槽過,相信前端同學有的也有過這樣的經歷,入職后發現自己是唯一的前端,而且接手的項目之前是后臺的同學開發的。素質高點的js不會亂引的太嚴重,然而大多數后臺的同學是不熟悉css的,樣式不僅冗余,而且很多因為優先級而相互覆蓋的問題。
換個角度想想就發現了css-modules的價值,就是tmd讓不熟css的同學乖乖的不要給人家挖坑。如果說真的需要接手這類的項目,即便是損失了css的靈活性,我也是會開心的笑出眼淚吧(畢竟比反復重構人家寫的頁面要好很多)。