歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

CSS Modules如何使用?

發(fā)布時間:2017-08-25 15:50  回復(fù):0  查看:2580   最后回復(fù):2017-08-25 15:50  
本文和大家分享的主要是CSS Modules的相關(guān)內(nèi)容,一起來看看吧,希望對大家學(xué)習(xí)css有所幫助。
  什么是css模塊化?
  為了理解css模塊化思想,我們首先了解下,什么是模塊化,在百度百科上的解釋是,在系統(tǒng)的結(jié)構(gòu)中,模塊是可組合、分解和更換的單元。模塊化是一種處理復(fù)雜系統(tǒng)分解成為更好的可管理模塊的方式。它可以通過在不同組件設(shè)定不同的功能,把一個問題分解成多個小的獨(dú)立、互相作用的組件,來處理復(fù)雜、大型的軟件。看完模塊化,是不是有種拼圖的即視感,可以把大圖分成各個小圖,然后把小圖拼成大圖,分與合的藝術(shù)感。那么css模塊化思想,也就是在css編寫環(huán)境中,用上模塊化的思想,把一個大的項目,分解成獨(dú)立的組件,不同的組件負(fù)責(zé)不同的功能,最后把模塊組裝,就成了我們要完成的項目了。
  css模塊化有什么好處?
  當(dāng)做一個大項目,幾個人團(tuán)隊合作開發(fā),結(jié)果看不懂彼此的代碼,怎么辦,當(dāng)面對前人已經(jīng)寫好代碼,需要修改,可是無處下手,怎么辦.當(dāng)代碼耦合,修改費(fèi)時費(fèi)力,怎么辦,當(dāng)需要迭代,面對龐大的代碼,牽一發(fā)動全身的悲催時刻,怎么辦,這個時候,模塊化思想就是救星了。css寫法特別的靈活,也因為靈活,所以容易耦合在一起,這時候就需要進(jìn)行模塊化的分離。那么css模塊化的好處多多,列舉了一些如下:
  · 提高代碼重用率
  · 提高開發(fā)效率、減少溝通成本
  · 提高頁面容錯
  · 降低耦合
  · 降低發(fā)布風(fēng)險
  · 減少Bug定位時間和Fix成本
  · 更好的實現(xiàn)快速迭代
  · 便于代碼維護(hù)
  CSS 模塊化的解決方案有很多,但主要有兩類。一類是徹底拋棄 CSS,使用 JS 或 JSON 來寫樣式。 Radium , jsxstyle , react-style 屬于這一類。優(yōu)點(diǎn)是能給 CSS 提供 JS 同樣強(qiáng)大的模塊化能力;缺點(diǎn)是不能利用成熟的 CSS 預(yù)處理器(或后處理器) Sass/Less/PostCSS, :hover和  :active 偽類處理起來復(fù)雜。另一類是依舊使用 CSS,但使用 JS 來管理樣式依賴,代表是 CSS Modules 。CSS Modules 能最大化地結(jié)合現(xiàn)有 CSS 生態(tài)和 JS 模塊化能力,API 簡潔到幾乎零學(xué)習(xí)成本。發(fā)布時依舊編譯出單獨(dú)的 JS 和 CSS。它并不依賴于  React ,只要你使用 Webpack,可以在 Vue/Angular/ jQuery 中使用。是我認(rèn)為目前最好的 CSS 模塊化解決方案。近期在項目中大量使用,下面具體分享下實踐中的細(xì)節(jié)和想法。
  CSS 模塊化遇到了哪些問題?
  CSS 模塊化重要的是要解決好兩個問題:CSS 樣式的導(dǎo)入和導(dǎo)出。靈活按需導(dǎo)入以便復(fù)用代碼;導(dǎo)出時要能夠隱藏內(nèi)部作用域,以免造成全局污染。Sass/Less/PostCSS 等前仆后繼試圖解決 CSS 編程能力弱的問題,結(jié)果它們做的也確實優(yōu)秀,但這并沒有解決模塊化最重要的問題。Facebook 工程師 Vjeux 首先拋出了 React 開發(fā)中遇到的一系列 CSS 相關(guān)問題。加上我個人的看法,總結(jié)如下:
  1.全局污染
  CSS 使用全局選擇器機(jī)制來設(shè)置樣式,優(yōu)點(diǎn)是方便重寫樣式。缺點(diǎn)是所有的樣式都是全局生效,樣式可能被錯誤覆蓋,因此產(chǎn)生了非常丑陋的  !important ,甚至 inline  !important 和復(fù)雜的 選擇器權(quán)重計數(shù)表 ,提高犯錯概率和使用成本。Web Components 標(biāo)準(zhǔn)中的 Shadow DOM 能徹底解決這個問題,但它的做法有點(diǎn)極端,樣式徹底局部化,造成外部無法重寫樣式,損失了靈活性。
  2.命名混亂 由于全局污染的問題,多人協(xié)同開發(fā)時為了避免樣式?jīng)_突,選擇器越來越復(fù)雜,容易形成不同的命名風(fēng)格,很難統(tǒng)一。樣式變多后,命名將更加混亂。
  3.依賴管理不徹底
  組件應(yīng)該相互獨(dú)立,引入一個組件時,應(yīng)該只引入它所需要的 CSS 樣式。但現(xiàn)在的做法是除了要引入 JS,還要再引入它的 CSS,而且 Saas/Less 很難實現(xiàn)對每個組件都編譯出單獨(dú)的 CSS,引入所有模塊的 CSS 又造成浪費(fèi)。JS 的模塊化已經(jīng)非常成熟,如果能讓 JS 來管理 CSS 依賴是很好的解決辦法。Webpack 的  css-loader 提供了這種能力。
  4.無法共享變量 復(fù)雜組件要使用 JS 和 CSS 來共同處理樣式,就會造成有些變量在 JS 和 CSS 中冗余,Sass/PostCSS/CSS 等都不提供跨 JS 和 CSS 共享變量這種能力。
  5.代碼壓縮不徹底 由于移動端網(wǎng)絡(luò)的不確定性,現(xiàn)在對 CSS 壓縮已經(jīng)到了變態(tài)的程度。很多壓縮工具為了節(jié)省一個字節(jié)會把 '16px' 轉(zhuǎn)成 '1pc'。但對非常長的 class 名卻無能為力,力沒有用到刀刃上。
  上面的問題如果只憑 CSS 自身是無法解決的,如果是通過 JS 來管理 CSS 就很好解決,因此 Vjuex 給出的解決方案是完全的 CSS in JS ,但這相當(dāng)于完全拋棄 CSS,在 JS 中以 Object 語法來寫 CSS,估計剛看到的小伙伴都受驚了。直到出現(xiàn)了 CSS Modules。
  CSS Modules 模塊化方案
  CSS Modules 內(nèi)部通過 ICSS 來解決樣式導(dǎo)入和導(dǎo)出這兩個問題。分別對應(yīng)  :import 和  :export 兩個新增的偽類。
  :import("path/to/dep.css") {
  localAlias: keyFromDep;
  /* ... */
  }:export {
  exportedKey: exportedValue;
  /* ... */
  }
  但直接使用這兩個關(guān)鍵字編程太麻煩,實際項目中很少會直接使用它們,我們需要的是用 JS 來管理 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 = `Submit`
  生成的 HTML 是
  <button class="button--normal-abc53"> Processing... </button>
  注意到 button--normal-abc5436 是 CSS Modules 按照  localIdentName 自動生成的 class 名。其中的  abc5436 是按照給定 算法 生成的序列碼。經(jīng)過這樣混淆處理后,class 名基本就是唯一的,大大降低了項目中樣式覆蓋的幾率。同時在生產(chǎn)環(huán)境下修改規(guī)則,生成更短的 class 名,可以提高 CSS 的壓縮率。
  上例中 console 打印的結(jié)果是:
  Object {
  normal: 'button--normal-abc546',
  disabled: 'button--disabled-def884',
  }
  CSS Modules 對 CSS 中的 class 名都做了處理,使用對象來保存原 class 和混淆后 class 的對應(yīng)關(guān)系。
  通過這些簡單的處理,CSS Modules 實現(xiàn)了以下幾點(diǎn):
  ·所有樣式都是 local 的,解決了命名沖突和全局污染問題
  ·class 名生成規(guī)則配置靈活,可以此來壓縮 class 名
  ·只需引用組件的 JS 就能搞定組件所有的 JS 和 CSS
  ·依然是 CSS,幾乎 0 學(xué)習(xí)成本
  樣式默認(rèn)局部
  使用了 CSS Modules 后,就相當(dāng)于給每個 class 名外加加了一個 :local ,以此來實現(xiàn)樣式的局部化,如果你想切換到全局模式,使用對應(yīng)的  :global 。
  .normal {
  color: green;
  }
  /* 以上與下面等價 */:local(.normal) {
  color: green;
  }
  /* 定義全局樣式 */:global(.btn) {
  color: red;
  }
  /* 定義多個全局樣式 */:global {
  .link {
  color: green;
  }
  .box {
  color: yellow;
  }
  }
  Compose 來組合樣式
  對于樣式復(fù)用,CSS Modules 只提供了唯一的方式來處理: composes 組合
  /* components/Button.css */.base { /* 所有通用的樣式 */ }
  .normal {
  composes: base;
  /* normal 其它樣式 */
  }
  .disabled {
  composes: base;
  /* disabled 其它樣式 */
  }
  import styles from './Button.css';
  buttonElem.outerHTML = `Submit`
  生成的 HTML 變?yōu)?br>   <button class="button--base-abc53 button--normal-abc53"> Processing...</button>
  由于在 .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 其它樣式 */
  }
  對于大多數(shù)項目,有了 composes 后已經(jīng)不再需要 Sass/Less/PostCSS。但如果你想用的話,由于  composes 不是標(biāo)準(zhǔn)的 CSS 語法,編譯時會報錯。就只能使用預(yù)處理器自己的語法來做樣式復(fù)用了。
  class 命名技巧
  CSS Modules 的命名規(guī)范是從 BEM 擴(kuò)展而來。BEM 把樣式名分為 3 個級別,分別是:
  ·Block:對應(yīng)模塊名,如 Dialog
  ·Element:對應(yīng)模塊中的節(jié)點(diǎn)名 Confirm Button
  ·Modifier:對應(yīng)節(jié)點(diǎn)相關(guān)的狀態(tài),如 disabled、highlight
  綜上,BEM 最終得到的 class 名為 dialog__confirm-button--highlight 。使用雙符號  __和  -- 是為了和區(qū)塊內(nèi)單詞間的分隔符區(qū)分開來。雖然看起來有點(diǎn)奇怪,但 BEM 被非常多的大型項目和團(tuán)隊采用。我們實踐下來也很認(rèn)可這種命名方法。
  CSS Modules 中 CSS 文件名恰好對應(yīng) Block 名,只需要再考慮 Element 和 Modifier。BEM 對應(yīng)到 CSS Modules 的做法是:
  /* .dialog.css */.ConfirmButton--disabled {
  }
  你也可以不遵循完整的命名規(guī)范,使用 camelCase 的寫法把 Block 和 Modifier 放到一起:
  /* .dialog.css */.disabledConfirmButton {
  }
  如何實現(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';
  // 會輸出 #F40console.log(style.primaryColor);
  CSS Modules 使用技巧
  CSS Modules 是對現(xiàn)有的 CSS 做減法。為了追求 簡單可控 ,作者建議遵循如下原則:
  ·不使用選擇器,只使用 class 名來定義樣式
  ·不層疊多個 class,只使用一個 class 把所有樣式定義好
  ·所有樣式通過 composes 組合來實現(xiàn)復(fù)用
  ·不嵌套
  上面兩條原則相當(dāng)于削弱了樣式中最靈活的部分,初使用者很難接受。第一條實踐起來難度不大,但第二條如果模塊狀態(tài)過多時,class 數(shù)量將成倍上升。
  一定要知道,上面之所以稱為建議,是因為 CSS Modules 并不強(qiáng)制你一定要這么做。聽起來有些矛盾,由于多數(shù) CSS 項目存在深厚的歷史遺留問題,過多的限制就意味著增加遷移成本和與外部合作的成本。初期使用中肯定需要一些折衷。幸運(yùn)的是,CSS Modules 這點(diǎn)做的很好:
  如果我對一個元素使用多個 class 呢?
  沒問題,樣式照樣生效。
  如何我在一個 style 文件中使用同名 class 呢?
  沒問題,這些同名 class 編譯后雖然可能是隨機(jī)碼,但仍是同名的。
  如果我在 style 文件中使用了 id 選擇器,偽類,標(biāo)簽選擇器等呢?
  沒問題,所有這些選擇器將不被轉(zhuǎn)換,原封不動的出現(xiàn)在編譯后的 css 中。也就是說 CSS Modules 只會轉(zhuǎn)換 class 名相關(guān)樣式。
  但注意,上面 3 個“如果”盡量不要發(fā)生。
  CSS Modules 結(jié)合 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
  });
  return <div className={styles.root}>
  <a className={cx}>Confirma>
  ...
  </div>
  }
  }
  注意,一般把組件最外層節(jié)點(diǎn)對應(yīng)的 class 名稱為 root 。這里使用了  classnames 庫來操作 class 名。
  如果你不想頻繁的輸入  styles.** ,可以試一下  react-css-modules ,它通過高階函數(shù)的形式來避免重復(fù)輸入  styles.** 。
  CSS Modules 結(jié)合歷史遺留項目實踐
  好的技術(shù)方案除了功能強(qiáng)大炫酷,還要能做到現(xiàn)有項目能平滑遷移。CSS Modules 在這一點(diǎn)上表現(xiàn)的非常靈活。
  外部如何覆蓋局部樣式
  當(dāng)生成混淆的 class 名后,可以解決命名沖突,但因為無法預(yù)知最終 class 名,不能通過一般選擇器覆蓋。我們現(xiàn)在項目中的實踐是可以給組件關(guān)鍵節(jié)點(diǎn)加上 data-role 屬性,然后通過屬性選擇器來覆蓋樣式。
  如
  // dialog.js
  return <div className={styles.root} data-role='dialog-root'>
  <a className={styles.disabledConfirm} data-role='dialog-confirm-btn'>Confirma>
  ...
  </div>
  // dialog.css
  [data-role="dialog-root"] {
  // override style
  }
  因為 CSS Modules 只會轉(zhuǎn)變類選擇器,所以這里的屬性選擇器不需要添加 :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 */// 以下為組件相關(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 中的樣式都是局部的。
  CSS Modules 很好的解決了 CSS 目前面臨的模塊化難題。支持與 Sass/Less/PostCSS 等搭配使用,能充分利用現(xiàn)有技術(shù)積累。同時也能和全局樣式靈活搭配,便于項目中逐步遷移至 CSS Modules。CSS Modules 的實現(xiàn)也屬輕量級,未來有標(biāo)準(zhǔn)解決方案后可以低成本遷移。如果你的產(chǎn)品中正好遇到類似問題,非常值得一試。


來源:極客頭條

您還未登錄,請先登錄

熱門帖子

最新帖子

?