本文首發於政採雲前端團隊博客: 如何在 React 中優雅的寫 CSS
問題:CSS 文件分離 != CSS 做用域隔離css
看下這樣的目錄結構:前端
├── src │ ├──...... # 公共組件目錄 │ ├── components # 組件 │ │ └──comA # 組件A │ │ ├──comA.js │ │ ├──comA.css │ │ └── index.js │ │ └──comB # 組件B │ │ ├──comB.js │ │ ├──comB.css │ │ └── index.js │ ├── routes # 頁面模塊 │ │ └── modulesA # 模塊A │ │ ├──pageA.js # pageA JS 代碼 │ │ ├──pageA.css # pageA CSS 代碼
看目錄結構清晰明瞭,因爲「 CSS 文件分離 != CSS 做用域隔離」這樣的機制,若是咱們不經過一些工具或規範來解決 CSS 的做用域污染問題,會產生非預期的頁面樣式渲染結果。react
假設咱們在組件 A 和組件 B import 引入 comA.css 和 comB.css。webpack
comA.cssgit
.title { color: red; }
comB.cssgithub
.title { font-size: 14px; }
最後打包出來的結果爲:web
.title { color: red; } .title { font-size: 14px; }
咱們但願,comA.css 二者互不影響,能夠發現,雖然 A、B 兩個組件分別只引用了本身的 CSS 文件,可是 CSS 並無隔離,兩個 CSS 文件是相互影響的!npm
隨着 SPA 的流行,js 能夠組件化,按需加載(路由按需加載、組件的 CSS 和 JS 都按需加載),這種狀況下 CSS 做用域污染的問題被放大,CSS 被按需加載後因爲 CSS 全局污染的問題,在加載出其餘一部分代碼後,可能致使現有的頁面上會出現詭異的樣式變更。這樣的問題加大了發佈的風險以及 debugger 的成本。後端
小編我從寫 Vue 到寫 React,Vue 的 scoped 完美的解決了 CSS 的做用域問題,那麼 React 如何解決 CSS 的做用域問題呢?sass
解決 React 的 CSS 做用域污染方案:
利用約定好的命名來隔離 CSS 的做用域
comA.css
.comA .title { color: red; } .comA .……{ …… }
comB.css
.comB .title { font-size: 14px; } .comB .……{ …… }
嗯,用 CSS 寫命名空間寫起來貌似有點累。
沒事咱們有 CSS 預處理器,利用 less、sass、stylus 等預處理器,代碼依然簡潔。
A.less
.comA { .title { color: red; } .…… { …… } }
B.less
.comB { .title { font-size: 14px; } .…… { …… } }
貌似很完美解決了 CSS 的做用域問題,可是問題來了,假設 AB 組件是嵌套組件。
那麼最後的渲染 DOM 結構爲:
<div class="comA"> <h1 class="title">組件A的title</h1> <div class="comB"> <h1 class="title">組件組件的title</h1> </div> </div>
comA 的樣式又成功做用在了組件 B 上。
不要緊,還有解,全部的 class 名以命名空間爲前綴。
<div class="comA"> <h1 class="comA__title">組件A的title</h1> <div class="comB"> <h1 class="comB__title">組件組件的title</h1> </div> </div>
A.less
.comA { &__title { color: red; } }
B.less
.comB { &__title { font-size: 14px; } }
若是,咱們的樣式還遵循 BEM (Block, Element, Modifier) 規範,那麼,樣式名簡直不要太長!可是問題確實也解決了,但約定畢竟是約定,靠約定和自覺來解決問題畢竟不是好方法,在多人維護的業務代碼中這種約定來解決 CSS 污染問題也變得很難。
使用 JS 語言寫 CSS,也是 React 官方有推薦的一種方式。
從React文檔進入
https://github.com/MicheleBertoli/css-in-js ,能夠發現目前的 CSS in JS 的第三方庫有60餘種。
看兩個比較大衆的庫:
支持 React、Redux、React Native、autoprefixed、Hover、僞元素和媒體查詢
看下官網文檔 :
const styles = reactCSS({ 'default': { card: { background: '#fff', boxShadow: '0 2px 4px rgba(0,0,0,.15)', }, }, 'zIndex-2': { card: { boxShadow: '0 4px 8px rgba(0,0,0,.15)', }, }, }, { 'zIndex-2': props.zIndex === 2, })
class Component extends React.Component { render() { const styles = reactCSS({ 'default': { card: { background: '#fff', boxShadow: '0 2px 4px rgba(0,0,0,.15)', }, title: { fontSize: '2.8rem', color: this.props.color, }, }, }) return ( <div style={ styles.card }> <div style={ styles.title }> { this.props.title } </div> { this.props.children } </div> ) } }
能夠看出,CSS 都轉化成了 JS 的寫法,雖然沒有學習成本,可是這種轉變仍是有一絲不適。
styled-components,目前社區裏最受歡迎的一款 CSS in JS 方案
const Button = styled.a` /* This renders the buttons above... Edit me! */ display: inline-block; border-radius: 3px; padding: 0.5rem 0; margin: 0.5rem 1rem; width: 11rem; background: transparent; color: white; border: 2px solid white; /* The GitHub button is a primary button * edit this to target it specifically! */ ${props => props.primary && css` background: white; color: palevioletred; `} ` render( <div> <Button href="https://github.com/styled-components/styled-components" target="_blank" rel="noopener" primary > GitHub </Button> <Button as={Link} href="/docs" prefetch> Documentation </Button> </div> )
與 reactCSS 不一樣,styled-components 使用了模板字符串,寫法更接近 CSS 的寫法。
利用 webpack 等構建工具使 class 做用域爲局部。
CSS 依然是仍是 CSS
例如 webpack ,配置 css-loader 的 options modules: true。
module.exports = { module: { rules: [ { test: /\.css$/, loader: 'css-loader', options: { modules: true, }, }, ], }, };
modules 更具體的配置項參考:https://www.npmjs.com/package/css-loader
loader 會用惟一的標識符 (identifier) 來替換局部選擇器。所選擇的惟一標識符以模塊形式暴露出去。
示例:
webpack css-loader options
options: { ..., modules: { mode: 'local', // 樣式名規則配置 localIdentName: '[name]__[local]--[hash:base64:5]', }, }, ...
App.js
... import styles from "./App.css"; ... <div> <header className={styles["header__wrapper"]}> <h1 className={styles["title"]}>標題</h1> <div className={styles["sub-title"]}>描述</div> </header> </div>
App.css
.header__wrapper { text-align: center; } .title { color: gray; font-size: 34px; font-weight: bold; } .sub-title { color: green; font-size: 16px; }
編譯後端的 CSS,classname 增長了 hash 值。
.App__header__wrapper--TW7BP { text-align: center; } .App__title--2qYnk { color: gray; font-size: 34px; font-weight: bold; } .App__sub-title--3k88A { color: green; font-size: 16px; }
(1)若是是 ui 組件庫中使用
建議使用 namespaces 方案
緣由:
(2)若是是業務代碼/業務組件中使用
CSS in JS / CSS Modules
業務代碼維護人員較多且不固定、代碼水平不一致,只經過規範來約束不靠譜,沒法保證開發人員嚴格遵照規範,不能根治 CSS 交叉影響問題,可是從 debug 角度考慮,建議組件外層都添加一個 namespaces 方面定位組件。而後加之 CSS in JS 或 CSS Modules 方案來解決 CSS 交叉影響問題。
CSS Modules 會比 CSS in JS 的侵入性更小,CSS in JS 能夠和 JS 共享變量,但我的更喜歡 CSS Modules ,可是誰優誰勝沒法武斷。
政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。
若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com