運用 CSS in JS 實現模組化

  • 2019 年 10 月 3 日
  • 筆記

一、什麼是 CSS in JS


上圖來源:https://2019.stateofcss.com/technologies/

CSS in JS 是2014年推出的一種設計模式,它的核心思想是把 CSS 直接寫到各自組件中,而不是單獨的樣式文件里。

CSS in js 的發展:

  • 最早就是內聯樣式

  • 依舊使用 CSS,但使用 JS 來管理樣式依賴,代表是 CSS Modules。

    這種方式在React框架中引入的。

  • 使用 JavaScript 生成 CSS 然後插入到頁面中的方式。例如 Styled Components。

    CSS Module 還是 JS 和 CSS 分離的寫法,而 styled components 實際上是在 JS 上寫 CSS了。

CSS in js 一次又一次的違背了 CSS 與 JS 分離的原則。

二、常見的 CSS in JS


1、CSS Modules

CSS Modules 能最大化地結合現有 CSS 生態和 JS 模組化能力,API 簡潔到幾乎零學習成本。

(1)安裝

CSS Modules 提供各種插件,支援不同的構建工具。本文使用的是 Webpack 的css-loader插件。

CSS Modules不局限於你使用哪個前端庫,無論是 React、Vue 還是 Angular,只要你能使用構建工具進行編譯打包就可以使用。

本文以 react 為例。下同。

(2)全局/局部作用域

CSS 是全局的,沒有局部作用域的功能。

但 CSS Modules 默認有局部作用域的概念,實現的方法為:使用獨一無二的 class 名

這個獨一無二的 class 名,是一個 hash 值,css-loader默認的生成演算法是[hash:base64],但 webpack 配置裡面可以自定義格式。

CSS Modules 只會對 className 以及 id 進行轉換,其他的比如屬性選擇器,標籤選擇器都不進行處理,推薦盡量使用 className。

寫法 – js:

import style from './App.css';    <h1 className={style.title_1}>  <h2 className={style.title_2}>

寫法 – css

# 局部作用域的兩種寫法  .title_1 {}    :local(.title_1) {}    # 全局作用域的兩種寫法  :global(.title_2) {}    :global {    .title_2 {}    # 還能繼續添加……  }

生成 – html:

<h1 class="_3zyde4l1yATCOkgn-DBWEL">    <h1 class="title_2">

生成 – css:

._3zyde4l1yATCOkgn-DBWEL {}    .title_2{}
(3)拓展 – 實現局部作用域的幾種做法

1、嵌套(很深)選擇器

.widget .table .row .cell .content .header .title {}

2、使用 BEM 的 class 命名規範

用很長的有規則的命名,來盡量實現唯一標識

className="widget__header--active"

參考我之前的文章《運用 CSS methodologies 去實現模組化》有介紹 BEM。

3、css modules 的做法

直接用 hash 生成 class 名。即沒有方法1的嵌套,也絕對不會出現方法2中小概率的命名衝突問題。

(4)組合 composition

composes關鍵字可以讓一個選擇器可以繼承另一個選擇器的規則

很像 less 里的繼承。

用處:

1、可以引入別的模組,是實現模組化的一個必要功能。

2、還能引入別的模組的部分樣式。

寫法 – css:

.title-base {    background-color: blue;  }    # 1、來源於本文件  .title {    composes: title-base;    color: red;  }    # 2、或 來源於別的文件  .title {    composes: title-base from './another.css';    color: green;  }

生成 – html:

<h1 class="_2DHwuiHWMnKTOYG45T0x34 _10B-buq6_BEOTOl9urIjf8">

生成 – css:

._10B-buq6_BEOTOl9urIjf8 {   background-color: blue;  }    ._2DHwuiHWMnKTOYG45T0x34 {    color: red;  }

注意:這裡是繼承不是 mixin,所以這裡沒有混入所繼承的選擇器的屬性,而是直接 addon 選擇器名。減少了重複程式碼。

(5)使用變數

方法1:PostCSS 和 postcss-modules-values

方法2:搭配 less / sass

:export 關鍵字可以把 CSS 中的 變數輸出到 JS 中。

/* config.scss */  $primary-color: #f40;    :export {    primaryColor: $primary-color;  }
/* app.js */    import style from 'config.scss';  // 會輸出 #F40  console.log(style.primaryColor);
(6)結合

1、跟 CSS 預處理器結合

2、跟 BEM 等 CSS methodologies 結合

可參考我之前的一篇文章:CSS methodologies 去實現模組化

(7)實例

react – jsx :

import classNames from 'classnames';    …………          <div className={styles.header}>          <ul className={styles.menu}>            <li>1</li>            <li>2</li>            <li>3</li>          </ul>          <div className={styles.account}>            <button              type="button"              className={classNames(styles.btnLogin, {                [styles.active]: !!this.state.active,              })}            >              login            </button>            <button type="button" className={styles.btnRegister}>              register            </button>          </div>        </div>

react – less :

.header {    color: blue;    .menu {      color: red;      li {        color: green;      }    }    .account {      color: orange;      .btnLogin {        font-size: 15px;      }      .btnRegister {        font-size: 20px;      }      .btnLogin.active,      .btnRegister.active {        font-weight: bold;      }    }  }

注意點:

1、因為使用了 css module 所以不用擔心類名重複。可以捨棄掉 BEM 那種很長的類名,在保證基本語意化的前提下採取盡量簡單的類名

2、建議類名為駝峰,因為 js 里的 dot 取值形式對駝峰友好,而對styles.btn-login 會報錯。

3、可在 less 中採取 Combined Class Names (如 .btnRegister.active)或 Nested Class Names

4、可以在 react 中用 classname 庫提高書寫效率(很適合搭配 state / props )。

# classname 用法  classNames('foo', 'bar'); // => 'foo bar'  classNames('foo', { bar: true }); // => 'foo bar'  classNames({ foo: true }, { bar: true }); // => 'foo bar'  classNames({ foo: true, bar: true }); // => 'foo bar'

5、寫好多的styles.xxx很煩怎麼辦?可以用 babel-plugin-react-css-modules 自動加 styles 前綴。例子:

import React from 'react';  import CSSModules from 'react-css-modules';  import styles from './table.css';    class Table extends React.Component {      render () {          return <div styleName='table'>              <div styleName='row'>                  <div styleName='cell'>A0</div>                  <div styleName='cell'>B0</div>              </div>          </div>;      }  }    export default CSSModules(Table, styles);

另外,還可以方便的覆蓋本地變數的樣式:

import customStyles from './table-custom-styles.css';<Table styles={customStyles} />;

6、更多實踐請參考 ant design pro (https://pro.ant.design/docs/style-cn),它們用的正是 css modules + less。

2、Styled Components

(1)安裝

npm install styled-components

(2)使用

就拿一個 demo 舉例:

import styled from 'styled-components';    const Wrapper = styled.section`    margin: 0 auto;    width: 300px;    text-align: center;  `;  const Button = styled.button`    width: 100px;    color: white;    background: skyblue;  `;    render(    <Wrapper>      <Button>Hello World</Button>    </Wrapper>  );

注意:Styled Components 不支援 less 和 sass 語法。


由於 Styled Components 有些激進,本人目前不打算深入了解。

So,剩餘部分待寫。

拓展


1、CSS 預處理器 和 CSS 後處理器

共同點:CSS 預處理器 和 CSS 後處理器 都屬於 CSS 處理器。

不同點:CSS 預處理器使用特殊的語法來標記需要轉換的地方,而 CSS 後處理器可以解析轉換標準的 CSS,並不需要任何特殊的語法。

CSS 後處理器的代表就是 PostCSS ,PostCSS 是一個平台,其中最常用到的插件就是 autoprefixer