CSS Modules

查小小飛發表於2019-12-24

CSS命名規範,基本又是每個團隊都要面臨的問題,規範必定會出一則,但最後能嚴格執行的,始終是一件很難的事。

剛來到公司團隊的時候,大家都遵守使用『基於姓氏命名』法則併為之推廣

CSS Modules

如下面常見的商品資訊模組結構:

CSS Modules

<div class="gb_goods_item">
	<div class="gb_goods_item_box">
	  <div class="gb_goods_item_image">
	    ...
	  </div>
	  <div class="gb_goods_item_info">
	    <div class="gb_goods_item_title">
	      ...
	    </div>
	    <div class="gb_goods_item_sidebar">
	      <div class="gb_goods_item_price">
	        ...
	      </div>
	      <div class="gb_goods_item_shopcart">
	        <div class="gb_shopcart">
	          ...
	        </div>
	      </div>
	    </div>
	  </div>
	</div>
</div>
複製程式碼

這套法則注重 CSS 語義的表達和管理,初衷就是為了方便區分樣式模組的歸屬,也方便日後複用,有點像 BEM 方法。但缺點也很明顯:難理解,命名需要花額個的時間考慮其獨特性,當『後代』結構程式碼相當複雜的時候,命名會太過沉長,不夠靈活,再有就是始終無法根治程式碼汙染和被汙染的問題。

剛開始使用的時候,相當不習慣,爭議也多,每次有新員工來都需要花不少時候解釋,亦很難讓之 100% 地樂意使用,即便有明確的團隊規範文件,真正上線的時候,還是會出現『不規範』的程式碼。

儘管如此,權衡了當時的利弊,還是堅持用了下來。當 Node 的出現和不斷強大,前端界得到了的極速的發展,前端工作流更為細緻。React 的橫空出世,突顯超大型高效協作收益的元件化更是深得人心,而元件化的獨立性及高複用性註定要解除 CSS 對元件的限制:樣式程式碼汙染問題。

『基於姓氏命名』顯然不能滿足這個要求,畢竟,CSS 樣式總是全域性作用的,『姓名』再獨特,總會有同名同姓的機會。此時,CSS Modules 提供了一個很好的解決方案。

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default.

CSS 檔案中的所有類名和動畫名的作用域都預設為當前作用域。

CSS Modules 給了 CSS 域的概念,先看一波 Css Modues 在實際應用上是怎麼的一回事。

React 元件中常見的輪播圖:

CSS Modules

JSX 的 Render 內容:

import styles from './styles/FocusSlider.css';

// FocusSlider
render() {
  return (
	  <div className={styles.fsSlider} data-role="fsSlider">
	    <div className={styles.fsSliderMain}>
	      <Slider
	        sliderData={this.state.sliderData}
	      />
	    </div>
	    <div className={styles.fsSliderBg}></div>
	    {/* 資料重載入提示 */}
	    <DataReqReload
	      isShow={this.state.isReloadData}
	      doReload={this.doReload}
	    />
	  </div>
  );
}
複製程式碼

對應該的 FocusSlider.scss

:local{
  .fsSlider{
    position: relative;
    padding-top: -webkit-calc(100vw * 120 / 1125);
    padding-top: calc(100vw * 120 / 1125);
  }

  .fsSliderMain{
    position: relative;
    z-index: 1;
    min-height: -webkit-calc( ((100vw - 60px) * 570 / 960) + ((100vw - 60px) * 70 / 1125) );
    min-height: calc( ((100vw - 60px) * 570 / 960) + ((100vw - 60px) * 70 / 1125) );
  }
  
  .fsSliderBg{
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: -webkit-calc(100vw * 588 / 1125);
    height: calc(100vw * 588 / 1125);
    background: #000 url(../../../images/top100/fs_bg.jpg) no-repeat;
    background-size: 100% auto;
  }

  .gbSlider{
    background: #000;
  }
}
複製程式碼

編譯後的程式碼是這樣的:

<div class="_1zbnOjlYzV-MBq1JhiGcZF" data-role="fsSlider">
    <div class="svXZ19tF2m_fbg8KuFb6T">
        <div class="lvyQyG394l5agFQKH8_du" data-role="gbSlider" style="opacity: 1;">
            ...
        </div>
    </div>
    <div class="_27Jm0ogj1EtOf2dNHKvKC1"></div>
    <!-- 資料重載入提示 -->
    <div class="f1qHBJK89yiiDn4Tcb2It _3grBcj2DcL0IJGuHXl4LK6" data-role="reloadBox">
        ...
    </div>
</div>
複製程式碼

可以看到,FocusSlider.scss 裡面對應定義的區域性域樣式名都變成了一個雜湊字串,同時FocusSlider.css檔案也會被編譯

._1zbnOjlYzV-MBq1JhiGcZF {
    position:relative;
    padding-top:calc(100vw * 120 / 1125)
}
.svXZ19tF2m_fbg8KuFb6T {
    position:relative;
    z-index:1;
    min-height:calc(((100vw - 60px) * 570 / 960) + ((100vw - 60px) * 70 / 1125))
}
._27Jm0ogj1EtOf2dNHKvKC1 {
    position:absolute;
    left:0;
    right:0;
    top:0;
    height:calc(100vw * 588 / 1125);
    background:#000 url(/static/media/fs_bg.70affa5b.jpg) no-repeat;
    background-size:100% auto
}
._2EHIbU8ioFFyvcDBD64P4_ {
    background:#000
}
複製程式碼

Css Modules 使得樣式的類名在區域性作用域內獨一無二而不會對全域性的樣式造成汙染,這正是元件化所急需的,似乎也是為 React 而生。

這樣一來,上面『基於姓氏命名』商品資訊結構用 Css Moduels 就可以這樣寫了:

import styles from '../styles/GoodsItem.css';

render() {
  return (
    <div class={styles.goodsItem}>
      <div class={styles.itemBox}>
        <div class={styles.image}>
          ...
        </div>
        <div class={styles.info}>
          <div class={styles.title}>
            ...
          </div>
          <div class={styles.sideBar}>
            <div class={styles.price}>
              ...
            </div>
            <div class={styles.shopCart}>
              <ShopCart
                sku={}
              />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
複製程式碼

這樣我們就可以更專注元件的開發了。那麼如何使用?其實用起來很簡單

  • 如果你是用 React,並且使用偉大的 Create React App,預設是支援 Css Modules 的。
  • 如果你是用 Vue,恭喜你,並不用為這個而操心,Vue 自帶樣式域的處理;
  • 如果都不是,那麼你可能需要 Webpack 的 css-loader,裡面你還可以定製雜湊類名。
  • 你還可以用 PostCSS-ModulesPostCSS 的一個外掛,在任何地方使用 Css Mudous ?

更具體的文件可以看一下 github 上的 css-modules

也可以看一下阮老師的《CSS Modules 用法教程》

Css Modules 用起來雖然很爽,但實際應用中,有一點美中不足,就是元件在區域性作用域下的定製問題,最常見的就是『換膚』需求了,如下圖

CSS Modules

上圖兩個商品列表,資料結構和資料來源都是一樣的,只是佈局和樣式略有不同,如果用傳統的 CSS 方案寫樣式,會先寫一個基準樣式,再寫『皮膚』樣式:

<div class="goods goods_a">
  <div class="goods_item">
    ...
  </div>
</div>

<div class="goods goods_b">
  <div class="goods_item">
    ...
  </div>
</div>
複製程式碼

如果在元件系統中,上面的商品列表中,有可能是這樣的結構:

在 Goods 的容器 GoodsContaniner 元件中

import styles from '../styles/GoodsContainer.css';

render() {
  return (
    <div className={style.goodsContainer}>
      <GoodsA>
        <GoodsItem />
      </GoodsA>

      <GoodsB>
        <GoodsItem />
      </GoodsB>
    </div>
  );
}
複製程式碼

如果 GoodsAGoodsBGoodsItem 都是獨立的元件,Css Modules 對應的樣式也是區域性作用域獨立的,GoodsAGoodsB 就不能對 GoodsItem 做樣式定製了。

對這種情況,可以在元件結構中為樣式寫好擴充套件屬性,如在 GoodsItem 元件中寫成:

import styles from '../styles/GoodsItem.css';

render() {
  return (
    <div className={style.goodsItem} data-role="goodsItem">
      ...
    </div>
  );
}
複製程式碼

這樣在 GoodsAGoodsB 中就可以控制到 GoodsItem

:local {
	.goodsA{
		[data-role="goodsItem"] {
			...
		}
	}
}
複製程式碼

可能你會問,如果這樣寫那豈不是很麻煩?這種場景完全可以通過預規劃來規避,如規劃好 GoodsItem 元件的樣式作用域範圍(global 作用域)或提前設計好元件的結構,預留引數對 GoodsItem 元件進行樣式控制。

但是實際專案迭代過程中,有時候我們是無法預知專案後續發展規模的,很大可能會在你第一版寫好的元件中,不斷迭代功能,如上面提到的『換膚』場景,這就很有必要寫好 data-role 為元件的樣式做擴充套件了。

如果有專案使用元件系統的,CSS 方案的挑選,Css Modules 是不二的選擇。

某電影的一句臺詞也許能準確描述用過 CSS Modules 後的心情:『如果你 x 過驢仔 o,就 x 唔翻轉頭了』

相關文章