摘要:上例中打印的結果是對中的名都做了處理,使用對象來保存原和混淆后的對應關系。結合實踐在處直接使用中名即可。如因為只會轉變類選擇器,所以這里的屬性選擇器不需要添加。
CSS 是前端領域中進化最慢的一塊。由于 ES2015/2016 的快速普及和 Babel/Webpack 等工具的迅猛發展,CSS 被遠遠甩在了后面,逐漸成為大型項目工程化的痛點。也變成了前端走向徹底模塊化前必須解決的難題。
CSS 模塊化的解決方案有很多,但主要有兩類。一類是徹底拋棄 CSS,使用 JS 或 JSON 來寫樣式。Radium,jsxstyle,react-style 屬于這一類。優點是能給 CSS 提供 JS 同樣強大的模塊化能力;缺點是不能利用成熟的 CSS 預處理器(或后處理器) Sass/Less/PostCSS,:hover 和 :active 偽類處理起來復雜。另一類是依舊使用 CSS,但使用 JS 來管理樣式依賴,代表是 CSS Modules。CSS Modules 能最大化地結合現有 CSS 生態和 JS 模塊化能力,API 簡潔到幾乎零學習成本。發布時依舊編譯出多帶帶的 JS 和 CSS。它并不依賴于 React,只要你使用 Webpack,可以在 Vue/Angular/jQuery 中使用。是我認為目前最好的 CSS 模塊化解決方案。近期在項目中大量使用,下面具體分享下實踐中的細節和想法。
CSS 模塊化遇到了哪些問題?CSS 模塊化重要的是要解決好兩個問題:CSS 樣式的導入和導出。靈活按需導入以便復用代碼;導出時要能夠隱藏內部作用域,以免造成全局污染。Sass/Less/PostCSS 等前仆后繼試圖解決 CSS 編程能力弱的問題,結果它們做的也確實優秀,但這并沒有解決模塊化最重要的問題。Facebook 工程師 Vjeux 首先拋出了 React 開發中遇到的一系列 CSS 相關問題。加上我個人的看法,總結如下:
全局污染
CSS 使用全局選擇器機制來設置樣式,優點是方便重寫樣式。缺點是所有的樣式都是全局生效,樣式可能被錯誤覆蓋,因此產生了非常丑陋的 !important,甚至 inline !important 和復雜的選擇器權重計數表,提高犯錯概率和使用成本。Web Components 標準中的 Shadow DOM 能徹底解決這個問題,但它的做法有點極端,樣式徹底局部化,造成外部無法重寫樣式,損失了靈活性。
命名混亂
由于全局污染的問題,多人協同開發時為了避免樣式沖突,選擇器越來越復雜,容易形成不同的命名風格,很難統一。樣式變多后,命名將更加混亂。
依賴管理不徹底
組件應該相互獨立,引入一個組件時,應該只引入它所需要的 CSS 樣式。但現在的做法是除了要引入 JS,還要再引入它的 CSS,而且 Saas/Less 很難實現對每個組件都編譯出多帶帶的 CSS,引入所有模塊的 CSS 又造成浪費。JS 的模塊化已經非常成熟,如果能讓 JS 來管理 CSS 依賴是很好的解決辦法。Webpack 的 css-loader 提供了這種能力。
無法共享變量
復雜組件要使用 JS 和 CSS 來共同處理樣式,就會造成有些變量在 JS 和 CSS 中冗余,Sass/PostCSS/CSS 等都不提供跨 JS 和 CSS 共享變量這種能力。
代碼壓縮不徹底
由于移動端網絡的不確定性,現在對 CSS 壓縮已經到了{{BANNED}}的程度。很多壓縮工具為了節省一個字節會把 "16px" 轉成 "1pc"。但對非常長的 class 名卻無能為力,力沒有用到刀刃上。
上面的問題如果只憑 CSS 自身是無法解決的,如果是通過 JS 來管理 CSS 就很好解決,因此 Vjuex 給出的解決方案是完全的 CSS in JS,但這相當于完全拋棄 CSS,在 JS 中以 Object 語法來寫 CSS,估計剛看到的小伙伴都受驚了。直到出現了 CSS Modules。
CSS Modules 模塊化方案CSS Modules 內部通過 ICSS 來解決樣式導入和導出這兩個問題。分別對應 :import 和 :export 兩個新增的偽類。
:import("path/to/dep.css") { localAlias: keyFromDep; /* ... */ } :export { exportedKey: exportedValue; /* ... */ }
但直接使用這兩個關鍵字編程太麻煩,實際項目中很少會直接使用它們,我們需要的是用 JS 來管理 CSS 的能力。結合 Webpack 的 css-loader 后,就可以在 CSS 中定義樣式,在 JS 中導入。
啟用 CSS Modules// webpack.config.js css?modules&localIdentName=[name]__[local]-[hash:base64:5]
加上 modules 即為啟用,localIdentName 是設置生成樣式的命名規則。
/* components/Button.css */ .normal { /* normal 相關的所有樣式 */ } .disabled { /* disabled 相關的所有樣式 */ }
/* components/Button.js */ import styles from "./Button.css"; console.log(styles); buttonElem.outerHTML = ``
生成的 HTML 是
注意到 button--normal-abc5436 是 CSS Modules 按照 localIdentName 自動生成的 class 名。其中的 abc5436 是按照給定算法生成的序列碼。經過這樣混淆處理后,class 名基本就是唯一的,大大降低了項目中樣式覆蓋的幾率。同時在生產環境下修改規則,生成更短的 class 名,可以提高 CSS 的壓縮率。
上例中 console 打印的結果是:
Object { normal: "button--normal-abc546", disabled: "button--disabled-def884", }
CSS Modules 對 CSS 中的 class 名都做了處理,使用對象來保存原 class 和混淆后 class 的對應關系。
通過這些簡單的處理,CSS Modules 實現了以下幾點:
所有樣式都是 local 的,解決了命名沖突和全局污染問題
class 名生成規則配置靈活,可以此來壓縮 class 名
只需引用組件的 JS 就能搞定組件所有的 JS 和 CSS
依然是 CSS,幾乎 0 學習成本
樣式默認局部使用了 CSS Modules 后,就相當于給每個 class 名外加加了一個 :local,以此來實現樣式的局部化,如果你想切換到全局模式,使用對應的 :global。
.normal { color: green; } /* 以上與下面等價 */ :local(.normal) { color: green; } /* 定義全局樣式 */ :global(.btn) { color: red; } /* 定義多個全局樣式 */ :global { .link { color: green; } .box { color: yellow; } }Compose 來組合樣式
對于樣式復用,CSS Modules 只提供了唯一的方式來處理:composes 組合
/* components/Button.css */ .base { /* 所有通用的樣式 */ } .normal { composes: base; /* normal 其它樣式 */ } .disabled { composes: base; /* disabled 其它樣式 */ }
import styles from "./Button.css"; buttonElem.outerHTML = ``
生成的 HTML 變為
由于在 .normal 中 composes 了 .base,編譯后會 normal 會變成兩個 class。
composes 還可以組合外部文件中的樣式。
/* settings.css */ .primary-color { color: #f40; } /* components/Button.css */ .base { /* 所有通用的樣式 */ } .primary { composes: base; composes: $primary-color from "./settings.css"; /* primary 其它樣式 */ }
對于大多數項目,有了 composes 后已經不再需要 Sass/Less/PostCSS。但如果你想用的話,由于 composes 不是標準的 CSS 語法,編譯時會報錯。就只能使用預處理器自己的語法來做樣式復用了。
class 命名技巧CSS Modules 的命名規范是從 BEM 擴展而來。BEM 把樣式名分為 3 個級別,分別是:
Block:對應模塊名,如 Dialog
Element:對應模塊中的節點名 Confirm Button
Modifier:對應節點相關的狀態,如 disabled、highlight
綜上,BEM 最終得到的 class 名為 dialog__confirm-button--highlight。使用雙符號 __ 和 -- 是為了和區塊內單詞間的分隔符區分開來。雖然看起來有點奇怪,但 BEM 被非常多的大型項目和團隊采用。我們實踐下來也很認可這種命名方法。
CSS Modules 中 CSS 文件名恰好對應 Block 名,只需要再考慮 Element 和 Modifier。BEM 對應到 CSS Modules 的做法是:
/* .dialog.css */ .ConfirmButton--disabled { }
你也可以不遵循完整的命名規范,使用 camelCase 的寫法把 Block 和 Modifier 放到一起:
/* .dialog.css */ .disabledConfirmButton { }如何實現CSS,JS變量共享
上面提到的 :export 關鍵字可以把 CSS 中的 變量輸出到 JS 中。下面演示如何在 JS 中讀取 Sass 變量:
/* config.scss */ $primary-color: #f40; :export { primaryColor: $primary-color; }
/* app.js */ import style from "config.scss"; // 會輸出 #F40 console.log(style.primaryColor);CSS Modules 使用技巧
CSS Modules 是對現有的 CSS 做減法。為了追求簡單可控,作者建議遵循如下原則:
不使用選擇器,只使用 class 名來定義樣式
不層疊多個 class,只使用一個 class 把所有樣式定義好
所有樣式通過 composes 組合來實現復用
不嵌套
上面兩條原則相當于削弱了樣式中最靈活的部分,初使用者很難接受。第一條實踐起來難度不大,但第二條如果模塊狀態過多時,class 數量將成倍上升。
一定要知道,上面之所以稱為建議,是因為 CSS Modules 并不強制你一定要這么做。聽起來有些矛盾,由于多數 CSS 項目存在深厚的歷史遺留問題,過多的限制就意味著增加遷移成本和與外部合作的成本。初期使用中肯定需要一些折衷。幸運的是,CSS Modules 這點做的很好:
如果我對一個元素使用多個 class 呢?
沒問題,樣式照樣生效。
如何我在一個 style 文件中使用同名 class 呢?
沒問題,這些同名 class 編譯后雖然可能是隨機碼,但仍是同名的。
如果我在 style 文件中使用了 id 選擇器,偽類,標簽選擇器等呢?
沒問題,所有這些選擇器將不被轉換,原封不動的出現在編譯后的 css 中。也就是說 CSS Modules 只會轉換 class 名相關樣式。
但注意,上面 3 個“如果”盡量不要發生。
CSS Modules 結合 React 實踐在 className 處直接使用 css 中 class 名即可。
/* dialog.css */ .root {} .confirm {} .disabledConfirm {}
import classNames from "classnames"; import styles from "./dialog.css"; export default class Dialog extends React.Component { render() { const cx = classNames({ [styles.confirm]: !this.state.disabled, [styles.disabledConfirm]: this.state.disabled }); returnConfirm ...} }
注意,一般把組件最外層節點對應的 class 名稱為 root。這里使用了 classnames 庫來操作 class 名。
如果你不想頻繁的輸入 styles.**,可以試一下 react-css-modules,它通過高階函數的形式來避免重復輸入 styles.**。
好的技術方案除了功能強大炫酷,還要能做到現有項目能平滑遷移。CSS Modules 在這一點上表現的非常靈活。
外部如何覆蓋局部樣式當生成混淆的 class 名后,可以解決命名沖突,但因為無法預知最終 class 名,不能通過一般選擇器覆蓋。我們現在項目中的實踐是可以給組件關鍵節點加上 data-role 屬性,然后通過屬性選擇器來覆蓋樣式。
如
// dialog.js returnConfirm ...
// dialog.css [data-role="dialog-root"] { // override style }
因為 CSS Modules 只會轉變類選擇器,所以這里的屬性選擇器不需要添加 :global。
如何與全局樣式共存前端項目不可避免會引入 normalize.css 或其它一類全局 css 文件。使用 Webpack 可以讓全局樣式和 CSS Modules 的局部樣式和諧共存。下面是我們項目中使用的 webpack 部分配置代碼:
module: { loaders: [{ test: /.jsx?$/, loader: "babel" }, { test: /.scss$/, exclude: path.resolve(__dirname, "src/styles"), loader: "style!css?modules&localIdentName=[name]__[local]!sass?sourceMap=true" }, { test: /.scss$/, include: path.resolve(__dirname, "src/styles"), loader: "style!css!sass?sourceMap=true" }] }
/* src/app.js */ import "./styles/app.scss"; import Component from "./view/Component" /* src/views/Component.js */ // 以下為組件相關樣式 import "./Component.scss";
目錄結構如下:
src ├── app.js ├── styles │ ├── app.scss │ └── normalize.scss └── views ├── Component.js └── Component.scss
這樣所有全局的樣式都放到 src/styles/app.scss 中引入就可以了。其它所有目錄包括 src/views 中的樣式都是局部的。
總結CSS Modules 很好的解決了 CSS 目前面臨的模塊化難題。支持與 Sass/Less/PostCSS 等搭配使用,能充分利用現有技術積累。同時也能和全局樣式靈活搭配,便于項目中逐步遷移至 CSS Modules。CSS Modules 的實現也屬輕量級,未來有標準解決方案后可以低成本遷移。如果你的產品中正好遇到類似問題,非常值得一試。
原發于知乎專欄 http://zhuanlan.zhihu.com/purerender/20495964
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/116377.html
摘要:能最大化地結合現有生態預處理器后處理器等和模塊化能力,幾乎零學習成本。編碼相關的所有樣式上例中打印的結果是注意到是按照自動生成的名。實踐手動引用渲染結果使用可以實現使用屬性自動加載模塊。 文章同步于Github Pines-Cheng/blog 隨著前端這幾年的風生水起,CSS作為前端的三劍客之一,各種技術方案也是層出不窮。從CSS prepocessor(SASS、LESS、Styl...
摘要:面試如何防騙一份優秀的前端開發工程師簡歷是怎么樣的作為,有哪些一般人我都告訴他,但是他都不聽的忠告如何面試前端工程師 更多資源請Star:https://github.com/maidishike... 文章轉自:https://github.com/jsfront/mo... 3月份前端資源分享 1. Javascript 使用judge.js做信息判斷 javascript...
摘要:為什么引入全局樣式沖突進行打包時,將所有文件導入到入口文件中,樣式也會統一加載到入口中,根據的規則,后面的樣式會覆蓋掉前面的樣式聲明,造成全局樣式的覆蓋問題。 CSS Modules 為什么引入CSS Modules (1)全局樣式沖突 webpack進行打包時,將所有js文件導入到入口App.js文件中,樣式也會統一加載到入口中,根據css的layout規則,后面的樣式會覆蓋掉前...
摘要:為什么引入全局樣式沖突進行打包時,將所有文件導入到入口文件中,樣式也會統一加載到入口中,根據的規則,后面的樣式會覆蓋掉前面的樣式聲明,造成全局樣式的覆蓋問題。 CSS Modules 為什么引入CSS Modules (1)全局樣式沖突 webpack進行打包時,將所有js文件導入到入口App.js文件中,樣式也會統一加載到入口中,根據css的layout規則,后面的樣式會覆蓋掉前...
摘要:更多相關介紹請看這特點僅僅只是虛擬最大限度減少與的交互類似于使用操作單向數據流很大程度減少了重復代碼的使用組件化可組合一個組件易于和其它組件一起使用,或者嵌套在另一個組件內部。在使用后,就變得很容易維護,而且數據流非常清晰,容易解決遇到的。 歡迎移步我的博客閱讀:《React 入門實踐》 在寫這篇文章之前,我已經接觸 React 有大半年了。在初步學習 React 之后就正式應用到項...
閱讀 797·2023-04-25 22:57
閱讀 3051·2021-11-23 10:03
閱讀 613·2021-11-22 15:24
閱讀 3156·2021-11-02 14:47
閱讀 2901·2021-09-10 11:23
閱讀 3115·2021-09-06 15:00
閱讀 3936·2019-08-30 15:56
閱讀 3322·2019-08-30 15:52