最近我对 CSS 模块特别感兴趣,要是你以前还从未听说过 CSS Modules,那么这个系列就是专门写给你的。首先我们会介绍一下 CSS Modules 是什么以及为什么要将 CSS 模块化,之后大概介绍一下如何应用 CSS Modules。最后如果你想进一步提升自己,可以阅读第三部分介绍的如何在 React 环境下使用 CSS Modules。

什么是 CSS Modules?

根据 CSS Modules 在 GitHub上的项目,它被解释为:

所有的类名和动画名称默认都有各自的作用域的 CSS 文件。

所以 CSS Modules 既不是官方标准,也不是浏览器的特性,而是在构建步骤(例如使用 Webpack 或 Browserify)中对 CSS 类名和选择器限定作用域的一种方式(类似于命名空间)。

我们还是先看一个具体的例子来解释清楚它到底是什么,以及为什么要使用 CSS Modules 吧。我们通常给 HTML加一个 CSS 的类名来控制它的样式:

<h1 class="title">An example heading</h1>

CSS 样式像下面这样:

.title {
  background-color: red;
}

只要把 CSS 文件加载到 HTML 文件中,这里的 <h1> 标签背景就会被设置成红色。我们不需要对 HTML 或 CSS 做什么特殊的处理,浏览器本来就支持这种最基本的文件类型。

而 CSS Modules 的使用方式就不一样了,我们需要把所有的标签写到JS文件里。下面是一个简单的示例:

import styles from "./styles.css";

element.innerHTML = 
  `<h1 class="${styles.title}">
     An example heading
   </h1>`;

在 JS 中你可以通过类似 styles.title 的方式访问 CSS 文件中的 .title 类。然后在构建过程中,我们的构建工具会搜索我们用 import 语句载入的名为 styles.css 的文件,之后把源文件解析成新的 HTML 和 CSS 文件,类名会被特定的格式替换:

HTML

<h1 class="_styles__title_309571057">
  An example heading
</h1>

CSS

._styles__title_309571057 {
  background-color: red;
}
v2-33634704e521458996f2f64ae77cac60_hd

类属型的 .title 完全被新生成的命名替换掉了,CSS 的源文件也不会被载入。

Hugo Giraudel 的教程 里也提到:

在使用 CSS 模块时,类名是动态生成的,唯一的,并准确对应到源文件中的各个类的样式。

这也是实现样式作用域的原理。它们被限定在特定的模板里。例如我们在 buttons.js 里引入 buttons.css 文件,并使用 .btn 的样式,在其他诸如 forms.js 里是不会被.btn影响的,除非它也引入了 buttons.css.

可我们是出于什么目的把 CSS 和 HTML 文件搞得这么零碎呢?我们为什么要使用 CSS 模块呢?

为什么要使用 CSS Modules?

通过 CSS Modules,我们可以保证单个组件的所有样式:

  1. 集中在同一个地方
  2. 只应用于该组件

另外,

import buttons from "./buttons.css";
import padding from "./padding.css";

element.innerHTML = `<div class="${buttons.red} ${padding.large}">`;

通过这样的方式可以解决 CSS 全局作用域的问题。

你一定经历过着急着赶工想要尽快写完 CSS,而根本没有考虑过你的代码会造成什么不良影响吧。

你也一定干过在某个样式文件的结尾加上随意命名的乱七八糟的样式之类吧。

你肯定也见过那些你不知道有什么效果,甚至到底有没有被使用的样式吧。

你难道不想安全地写出不影响其他样式的 CSS 么?担心样式是独立或者依赖别的什么东西么?或者可能会覆盖别的地方的样式?

诸如此类的问题都足够让人头疼了。而且随着项目的扩张,会越来越令人绝望。

但使用 CSS Modules 就可以避免这些问题。除非你在某个模块中载入一个 CSS 样式,其他情况下这个样式都不会影响别的 HTML。

composes 关键词属性

假如我们有如下一个名为 type.css 的样式文件:

.serif-font {
  font-family: Georgia, serif;
}

.display {
  composes: serif-font;
  font-size: 30px;
  line-height: 35px;
}

之后我们在模板里声明其中的第二个属性:

import type from "./type.css";

element.innerHTML = 
  `<h1 class="${type.display}">
    This is a heading
  </h1>`;

生成的结果大概是这个样子:

<h1 class="_type__display_0980340 _type__serif_404840">
  Heading title
</h1>

样式中的两个属性通过 composes 关键字都加载到了该元素上,实现了类似 Sass 中 @extend 的功能。

我们甚至可以通过 composes 继承其他文件中的样式:

.element {
  composes: dark-red from "./colors.css";
  font-size: 30px;
  line-height: 1.2;
}

BEM 代码规范不再是必须的

在编写 CSS 模块时我们不再需要遵守 BEM 规范,有这么两个原因:

  1. 简化语法——在 CSS Modules 中通过类似 type.display 的表达已经能够很好地被理解了。比起某些 BEM 中例如 .font-size__serif--large 这样的命名要更清晰。
  2. 作用域——比如我们的一个 CSS 中的 .big 是用来描述文字大小的,另一个 CSS 中的 .big 是用来增加间距的。通过使用 CSS Modules,我们在能够随意使用相同命名类的同时,更不用担心它们之间起冲突。即使是在同一个文件中引入这两个 CSS,最后生成的结果也会把两个样式区别开来。

这仅仅是使用 CSS Modules 的一点点好处而已。

如果你想了解更多 CSS Modules 的优点,可以看 Glen Madden 写的这篇文章

在下一篇中,我们将会讨论如何在项目中通过 Webpack 来使用 CSS Modules,通过使用一些 ES6 的最新特性以及具体的例子来学习。

原文链接:https://css-tricks.com/css-modules-part-1-need/,作者:Robin Rendle