雜談 CSS IN JS

前言

關注點分離(separation of concerns)原則多年來大行其道,實踐中通常將 HTMLCSSJavaScript 分開編寫維護,早期框架 angularjs 便是如此,直到 React 爭議中問世,引領關注點混合趨勢,驅使開發者從新審視 CSS 工程化發展。javascript

尷尬的CSS

相對於 JavaScript 的日新月異,CSS 的發展緩慢,相對止步不前。隨着前端職能擴大化成爲常態,前端工程化日趨成熟,CSS 先天缺陷愈發明顯:css

  • 全局做用域
  • 缺少高級編程特性
  • 代碼冗餘
  • 極限壓縮
  • 依賴管理

最大的缺陷 來自於全局做用域,class name 全局生效,多人協做中的風格不一致,隨時可能引起蝴蝶效應。爲規避多人協做的風格衝突,社區提出 OOCSSBEM 等方法論,但實踐中徹底取決於團隊執行力度,筆者也曾苦惱於合理的命名,爲避免衝突,致使類名冗長,無聊且痛苦。前端

缺少高級編程特性 影響一樣深遠,社區發展的預處理器可以有效緩解,sasslessstylus異曲同工,postcss 異軍突起,基本實現變量、嵌套、變量、混合、擴展和邏輯等。隨着 CSS 規範逐步推動,高級編程特性徹底可期。筆者大膽斷言,前端工程化的推動,已經基本解決 CSS高級編程特性缺少 的問題。java

代碼冗餘,極限壓縮對開發的影響相對很小,經典的 bootstrap 就包含大量的冗餘代碼,但絲絕不影響其流行程度。react

目前難以解決的是依賴管理,NPM 已經成爲事實上的 JavaScript 包管理工具,而 CSS 始終沒有發展出可用的管理模式,sass 的淺嘗輒止,例如 bootstrap-sass, Bourbon等,顯然沒法知足需求。隨着 React 引領的關注點混合,以組件爲核心的開發模式,有效規避了 CSS 缺少依賴管理的缺陷,筆者認爲,依賴管理弊端徹底可控,將來的發展,留給將來述說。angularjs

新銳的組件化

前端發展突飛猛進,React 在衆人爭議中進入視野,典型的 React 組件同時包含結構、樣式、行爲,示例以下:編程

/** * @description - lite component * @author - huang.jian <hjj491229492@hotmail.com> */
export class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      timestamp: Date.now()
    };
  }
  
  render() {
    return (
      <Card title="React Timestamp">
        <Alert message={`React Timestamp: ${this.state.timestamp}`} type="success"/>
        <Alert message={`React Timestamp: ${this.state.timestamp}`} type="info"/>
        <Alert message={`React Timestamp: ${this.state.timestamp}`} type="warning"/>
      </Card>
    );
  }
}
複製代碼

前端應用由組件聚合而成,組件層面對 CSS 進行抽象,從而解決大型應用的 CSS 維護難題。社區出現的 CSS IN JS 解決方案,目前看來就是可行解決方案,其本質在於經過 JavaScript 來聲明,維護樣式,以 styled-components 舉例:bootstrap

const Button = styled.button`
  border-radius: 3px;
  padding: 0.25em 1em; 
  color: palevioletred;
  border: 2px solid palevioletred; 
`;

function Buttons() {
  return (
    <Button>Normal Button</Button>
    <Button primary>Primary Button</Button>
  );
}
複製代碼

樣式寄生組件之中,組件掛載時,動態插入樣式,實現按需加載,動態生成類名,隔離做用域。另一種思路,經過 style 屬性傳入內嵌樣式,徹底規避選擇器全局做用域的問題。前端工程化

// 官方示例有刪減
var Radium = require('radium');
var React = require('react');
var color = require('color');

// You can create your style objects dynamically or share them for
// every instance of the component.
var styles = {
  base: {
    color: '#fff',
  },

  primary: {
    background: '#0074D9'
  },

  warning: {
    background: '#FF4136'
  }
};

@Radium
class Button extends React.Component {
  render() {
    return (
      <button style={[ styles.base, styles[this.props.kind] ]}> {this.props.children} </button>
    );
  }
}
複製代碼

面向組件開發,爲樣式管理提供更多的可能性,徹底使用 JavaScript 抽象,管理,維護樣式,略顯激進,但也不失爲一種解決方案。sass

客觀的分析

目前主流的 CSS IN JS 方案與傳統的方式對好比下:

優點:

  • 隔離做用域 -- 樣式生效經過內嵌,或者生成獨一無二的類名,避免出現選擇器衝突;
  • 高級編程特性 -- 充分利用 JavaScript 的能力加強對樣式的控制;
  • 樣式按需掛載 -- 頁面須要的樣式纔會加載,有效避免樣式冗餘;
  • 依賴管理 -- 寄生於組件,利用現存的 NPM 生態進行包管理;
  • 動態樣式 -- 可以更加簡單,直接的修改樣式

劣勢:

  • 沒法複用現有生態,特性徹底依賴於庫的實現;
  • 編輯器代碼補全,語法檢查,語法高亮等須要插件支持;
  • 僞類選擇器(disabled、:before、:nth-child)支持詭異;
  • 樣式屬性駱駝式命名;

獨闢蹊徑

筆者並不徹底認同 CSS IN JS 的理念,也不反對將其應用於生產項目。CSS 中最嚴重的問題,不經過 CSS-in-JS 也能 有其餘解決方案,也就是筆者當前使用的 CSS Module 方案。經過工程化的方式,將選擇器編譯爲獨一無二的類名,使用 JavaScript 管理選擇器與元素的關聯,僅此而已。

// Header.jsx
import style from './Header.css'

// { header: 'Header__header--3kSIq_0' }

export default function Header() {
  return (<div className={style.header}>Header!!!</div>);
}
複製代碼

優點:

  • 隔離做用域 -- 類名編譯生成,有效避免選擇器衝突;
  • 樣式按需加載 -- 利用 tree-shaking 機制,僅保留存在引用的選擇器,有效避免樣式冗餘;
  • 依賴管理 -- 關聯組件,利用現存的 NPM 機制進行包管理;
  • 充分利用現有生態 -- 編輯器高亮,自動補全,sasspostcss高級編程特性;

劣勢:

  • 欠缺動態樣式特性 -- 沒法充分利用 JavaScript 的能力加強對樣式的控制;

主觀的感悟

本文未涉及的 單文件組件 也是可行方案之一,目前 VueAngular 等框架採用。筆者始終認爲,與其創造更多抽象的技術讓前端學習曲線更加陡峭,不如經過工程化的手段來修復存在的缺陷,理念上求同存異。面對各類技術方案,適合實際項目的方案纔是最好的方案,選用預處理器 PostCSSBEM,亦或動態編譯,都須要結合業務場景、團隊習慣等因素決策。

關注公衆號,獲取動態,支持做者。

qrcode_for_gh_d8efb59259e2_258.jpg-26.1kB
相關文章
相關標籤/搜索