CSS 模块化的实现方式

BEM 命名规范、CSS Modules 和 CSS in JS 三种方案对比

问题

如何实现 CSS 模块化,避免样式冲突和命名污染?

解答

BEM 命名规范

BEM 由 Yandex 团队提出,通过块(Block)、元素(Element)、修饰符(Modifier)的命名约定来组织 CSS 代码。

/* 块:独立的组件或模块 */
.block {
}

/* 元素:块中的组成部分,不能脱离块使用 */
.block__element {
}

/* 修饰符:定义块或元素的外观和行为 */
.block--modifier {
}

BEM 通过严格的命名规范让代码结构清晰,但本质上是约定而非强制,无法完全避免命名冲突。

CSS Modules

CSS Modules 像导入 JS 模块一样导入 CSS,打包时自动将类名转换为 hash 值,彻底解决命名冲突。

定义样式文件:

.className {
  color: green;
}

/* 全局样式 */
:global(.className) {
  color: red;
}

/* 样式复用 */
.otherClassName {
  composes: className;
  color: yellow;
}

在 JS 中使用:

import styles from "./style.css";

element.innerHTML = '<div class="' + styles.className + '">';

配置 webpack:

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: {
          loader: 'css-loader',
          options: {
            modules: true
          }
        }
      }
    ]
  }
};

打包后的类名:

._2DHwuiHWMnKTOYG45T0x34 {
  color: red;
}

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

CSS in JS

使用 JS 编写 CSS,所有样式代码放在组件内部。目前有 40+ 种实现方案,最流行的是 styled-components。

import React from "react";
import styled from "styled-components";

// 创建带样式的组件
const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

// 通过属性动态定义样式
const Button = styled.button`
  background: ${props => props.primary ? "palevioletred" : "white"};
  color: ${props => props.primary ? "white" : "palevioletred"};
`;

其他流行的 CSS in JS 库:emotion、radium、glamorous。

关键点

  • BEM 通过命名约定实现模块化,但依赖开发者自觉遵守规范
  • CSS Modules 自动生成 hash 类名,彻底避免命名冲突,需要构建工具支持
  • CSS in JS 将样式写在组件内部,支持动态样式,完全不需要单独的 CSS 文件
  • 三种方案各有优劣:BEM 简单但不强制,CSS Modules 需要配置,CSS in JS 运行时开销较大