CSS Modules 入門及 React 中實踐

lvchongyi 7年前發布 | 14K 次閱讀 CSS React 前端技術

寫在前面

讀文先看此圖,能先有個大體概念:

CSS Modules介紹

CSS Modules是什么東西呢?首先,讓我們從官方文檔入手:

GitHub – css-modules/css-modules: Documentation about css-modules

CSS模塊是一個CSS文件,其中所有類名和動畫名稱在本地默認范圍. CSS模塊就是所有的類名都只有局部作用域的CSS文件。

所以CSS Modules既不是官方標準,也不是瀏覽器的特性,而是在構建步驟(例如使用Webpack或Browserify)中對CSS類名選擇器限定作用域的一種方式(通過hash實現類似于命名空間的方法)。

It doesn’t really matter in the end (although shorter class names mean shorter stylesheets) because the point is that they are dynamically generated, unique, and mapped to the correct styles.在使用CSS模塊時,類名是動態生成的,唯一的,并準確對應到源文件中的各個類的樣式。

這也是實現樣式作用域的原理。它們被限定在特定的模板里。例如我們在buttons.js里引入buttons.css文件,并使用.btn的樣式,在其他組件里是不會被.btn影響的,除非它也引入了buttons.css.

可我們是出于什么目的把CSS和HTML文件搞得這么零碎呢?我們為什么要使用CSS模塊呢?

為什么我們需要CSS模塊化

CSS全局作用域問題

CSS的規則都是全局的,任何一個組件的樣式規則,都對整個頁面有效。相信寫css的人都會遇到樣式沖突(污染)的問題。

于是一般這么做(筆者都做過):

* class命名寫長一點吧,降低沖突的幾率

* 加個父元素的選擇器,限制范圍

* 重新命名個class吧,比較保險

所以亟待解決的問題就是css局部作用域避免全局樣式沖突(污染)的問題

JS CSS無法共享變量

復雜組件要使用 JS 和 CSS 來共同處理樣式,就會造成有些變量在 JS 和 CSS 中冗余,CSS預處理器/后處理器 等都不提供跨 JS 和 CSS 共享變量這種能力。

健壯并且擴展方便的CSS

作為有追求的工程師,編寫健壯并且擴展方便的CSS一直是我們的目標。那么如何定義健壯并且擴展方便?有三個要點:

  • 面向組件 – 處理 UI 復雜性的最佳實踐就是將 UI 分割成一個個的小組件 Locality_of_reference 。如果你正在使用一個合理的框架,JavaScript 方面就將原生支持(組件化)。舉個例子,React 就鼓勵高度組件化和分割。我們希望有一個 CSS 架構去匹配。
  • 沙箱化(Sandboxed) – 如果一個組件的樣式會對其他組件產生不必要以及意想不到的影響,那么將 UI 分割成組件并沒有什么用。就這方面而言,CSS的全局作用域會給你造成負擔。
  • 方便 – 我們想要所有好的東西,并且不想產生更多的工作。也就是說,我們不想因為采用這個架構而讓我們的開發者體驗變得更糟。可能的話,我們想開發者體驗變得更好。

CSS模塊化方案分類

CSS 模塊化的解決方案有很多,但主要有三類。

CSS 命名約定

規范化CSS的模塊化解決方案(比如BEM BEM — Block Element Modifier ,OOCSS,AMCSS,SMACSS,SUITCSS)

但存在以下問題:

* JS CSS之間依然沒有打通變量和選擇器等

* 復雜的命名

CSS in JS

徹底拋棄 CSS,用 JavaScript 寫 CSS 規則,并內聯樣式。 React: CSS in JS // Speaker Deck 。Radium,react-style 屬于這一類。但存在以下問題:

* 無法使用偽類,媒體查詢等

* 樣式代碼也會出現大量重復。

* 不能利用成熟的 CSS 預處理器(或后處理器)

使用JS 來管理樣式模塊

使用JS編譯原生的CSS文件,使其具備模塊化的能力,代表是 CSS Modules GitHub – css-modules/css-modules: Documentation about css-modules

CSS Modules 能最大化地結合現有 CSS 生態(預處理器/后處理器等)和 JS 模塊化能力,幾乎零學習成本。只要你使用 Webpack,可以在任何項目中使用。是筆者認為目前最好的 CSS 模塊化解決方案。

CSS Modules 使用教程

啟用 CSS Modules

// webpack.config.js
css?modules&localIdentName=[name]__[local]-[hash:base64:5]
 

加上 modules 即為啟用, localIdentName 是設置生成樣式的命名規則。

/* components/Button.css */
.normal { /* normal 相關的所有樣式 */ }
 
// components/Button.js
importstylesfrom './Button.css';
console.log(styles);
buttonElem.outerHTML = `<buttonclass=${styles.normal}>Submit</button>`
 

生成的 HTML 是

<buttonclass="button--normal-abc53">Submit</button>
 

注意到 button--normal-abc53 是 CSS Modules 按照 localIdentName 自動生成的 class 名。其中的 abc53 是按照給定算法生成的序列碼。經過這樣混淆處理后,class 名基本就是唯一的,大大降低了項目中樣式覆蓋的幾率。同時在生產環境下修改規則,生成更短的 class 名,可以提高 CSS 的壓縮率。

上例中 console 打印的結果是:

Object {
  normal: 'button--normal-abc53',
  disabled: 'button--disabled-def886',
}
 

CSS Modules 對 CSS 中的 class 名都做了處理,使用對象來保存原 class 和混淆后 class 的對應關系。

通過這些簡單的處理,CSS Modules 實現了以下幾點:

* 所有樣式都是局部作用域 的,解決了全局污染問題

* class 名生成規則配置靈活,可以此來壓縮 class 名

* 只需引用組件的 JS 就能搞定組件所有的 JS 和 CSS

* 依然是 CSS,幾乎 0 學習成本

CSS Modules 在React中的實踐

那么我們在React中怎么使用?

手動引用解決

在 className 處直接使用 css 中 class 名即可。

importReactfrom 'react';
importstylesfrom './table.css';
 
exportdefault class Tableextends React.Component {
    render () {
        return <divclassName={styles.table}>
            <divclassName={styles.row}>
            </div>
        </div>;
    }
}
 

渲染出來的組件出來

<divclass="table__table___32osj">
    <divclass="table__row___2w27N">
    </div>
</div>
 

react-css-modules

如果你不想頻繁的輸入 styles.** ,有一個 GitHub – gajus/react-css-modules: Seamless mapping of class names to CSS modules inside of React components. ,它通過高階函數的形式來生成 className ,不過不推薦使用,后文會提到。

API也很簡單,給組件外包一個CSSModules即可。

importReactfrom 'react';
importCSSModulesfrom 'react-css-modules';
importstylesfrom './table.css';
 
class Tableextends React.Component {
    render () {
        return <divstyleName='table'>
        </div>;
    }
}
 
exportdefault CSSModules(Table, styles);
 

不過這樣我們可以看到,它是需要運行時的依賴,而且需要在運行時才獲取className,性能損耗大,那么有沒有方便又接近無損的方法呢?答案是有的,使用babel插件 babel-plugin-react-css-modules GitHub – gajus/babel-plugin-react-css-modules: Transforms styleName to className using compile time CSS module resolution. 把 className 獲取前置到編譯階段。

babel-plugin-react-css-modules

babel-plugin-react-css-modules 可以實現使用 styleName 屬性自動加載CSS模塊。我們通過該babel插件來進行語法樹解析并最終生成 className 。

來看看組件的寫法,現在你只需要把 className 換成 styleName 即可獲得CSS局部作用域的能力了,是不是非常簡單。

importReactfrom 'react';
importstylesfrom './table.css';
 
class Tableextends React.Component {
    render () {
        return <divstyleName='table'>
        </div>;
    }
}
 
exportdefault Table;
 

工作原理

那么該babel插件是怎么工作的呢?讓我們從官方文檔入手:

GitHub – gajus/babel-plugin-react-css-modules: Transforms styleName to className using compile time CSS module resolution.

筆者不才 ,稍作翻譯如下:

1. 構建每個文件的所有樣式表導入的索引(導入具有 .css 或 .scss 擴展名的文件)。

2. 使用 postcss 解析匹配到的css文件

3. 遍歷所有 JSX 元素聲明

4. 把 styleName 屬性解析成匿名和命名的局部css模塊引用

5. 查找與CSS模塊引用相匹配的CSS類名稱:

* 如果 styleName 的值是一個字符串字面值,生成一個字符串字面值。

* 如果是JSXExpressionContainer,在運行時使用helper函數來構建如果 styleName 的值是一個 jSXExpressionContainer , 使用輔助函數([ getClassName ]在運行時構造 className 值。

6. 從元素上移除 styleName 屬性。

7. 將生成的 className 添加到現有的 className 值中(如果不存在則創建 className 屬性)。

使用實例

在成熟的項目中,一般都會用到CSS預處理器或者后處理器。

這里以使用了 stylus CSS預處理器為例子,我們來看下如何使用。

  • 安裝依賴
npminstall -save-devsugerssbabel-plugin-react-css-modules 
 
  • 編寫Webpack配置
// webpack.config.js
module: {
  loaders: [{
    test: /\.js?$/,
    loader: [['babel-plugin-react-css-modules',{
          generateScopedName:'[name]__[local]',
          filetypes: {
              ".styl": "sugerss"
          }
    }]]
  }, {
    test: /\.module.styl$/,
    loader: 'style!css?modules&localIdentName=[name]__[local]!styl?sourceMap=true'
  }, {
    test: /\.styl$/,
    loader: 'style!css!styl?sourceMap=true'
  }]
}
 
  • 組件寫法
importReactfrom 'react';
import './table.module.styl';
 
class Tableextends React.Component {
    render () {
        return <divstyleName='table'>
        </div>;
    }
}
 
exportdefault Table;
 

如上,你可以通過配置Webpack中module.loaders的test路徑 Webpack-module-loaders-configuration ,來區分樣式文件是否需要CSS模塊化。

搭配 sugerss 這個 postcss 插件作為 stylus 的語法加載器,來支持babel插件 babel-plugin-react-css-modules 的語法解析。

最后我們回過頭來看下,我們React組件只需要把 className 換成 styleName ,搭配以上構建配置,即可實現CSS模塊化 。

最后

CSS Modules 很好的解決了 CSS 目前面臨的模塊化難題。支持與 CSS處理器搭配使用,能充分利用現有技術積累。如果你的產品中正好遇到類似問題,非常值得一試。

希望大家都能寫出健壯并且可擴展的CSS,以上。

 

來自:http://www.alloyteam.com/2017/03/getting-started-with-css-modules-and-react-in-practice/

 

 本文由用戶 lvchongyi 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!