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