CSS模块化

本文最后更新于 2025年8月2日 晚上

参考:

概览

模块化 CSS(Modular CSS)是指把页面分割成不同的组成部分,这些组成部分可以在多种上下文中重复使用,并且互相之间没有依赖关系。最终目的是,当我们修改其中一部分 CSS 时不会对其他部分产生意料之外的影响。

在计算机科学中,编写模块化代码并不是什么新潮的做法,但开发人员近几年才开始将其引入 CSS。随着现代网站和 Web 应用程序体量越来越大、越来越复杂,我们不得不寻找一些方法来管理日益庞大且繁杂的样式表。

我们把样式表的每个组成部分称为模块(module) ,每个模块独立负责自己的样式,不会影响其他模块内的样式。也就是说,在 CSS 里引入了软件封装的原则。封装(encapsulation):相关的函数和数据集合在一起组成对象,通常用来隐藏结构化对象内部的状态或值,从而使外部因素不能操作对象内部。

CSS 中没有数据和传统函数的概念,但是有选择器及其命中的页面元素。为了达到封装的目的,这些会成为模块的组成部分,并且每个模块都只负责少量的 DOM 元素的样式。有了封装的思想,我们就可以为页面上那些彼此分立的组件定义模块了,像导航菜单、对话框、进度条、缩略图,等等。可以通过为 DOM 元素设置一个独一无二的的类名来识别每个模块。同时,每个模块包含一系列子元素,构建成页面上的组件。模块内部可以嵌套其他模块,最终构成完整的页面。

基础样式

开始写模块化样式前需要先配置好环境。每个样式表的开头都要写一些给整个页面使用的通用规则,模块化 CSS 也不例外。这些规则通常被称为基础样式,其他的样式构建在这些基础样式之上的。基础样式本身并不是模块化的,但它会为后面编写模块化样式打好基础。

1
2
3
4
5
6
7
8
9
:root {
box-sizing: border-box;
}

*,
*::before,
*::after {
box-sizing: inherit;
}

常用的基础样式还包括链接的颜色、标题的样式、外边距等。<body>标签默认的外边距很小,你可能会考虑将它的外边距去掉。根据项目的实际情况,你也可能想为表单字段、表格和列表等添加一些样式。

基础样式应该是通用的,只添加那些影响页面上大部分或者全部内容的样式。选择器不应该使用类名或者 ID 来匹配元素,应只用标签类型或者偶尔用用伪类选择器。核心思想是这些基础样式提供了一些默认的渲染效果,但是之后可以很方便地根据需要覆盖基础样式。基础样式配置完成以后,很少会再修改。我们会在基础样式的稳定表现之上构建模块化 CSS。在样式表中,基础样式后面的内容将主要由各种模块组成。

创建模块

模块的选择器由单个类名构成,这非常重要。选择器里没有其他规则来约束这些样式仅作用在页面上的某个地方。

创建模块不但可以精简代码(减少重复),还可以保证视觉一致性。这样看上去更专业,不会给人仓促堆砌的感觉。用户在潜意识里也会更容易相信我们的应用程序。

模块变体

保持一致性确实不错,但有时候需要特意避免一致。比如,我们可能想要区分传递信息的消息和表示操作成功的通知(比如保存成功)。这可以通过定义修饰符(modifiers)来实现。

通过定义一个以模块名称开头的新类名来创建一个修饰符。例如,消息模块的 error 修饰符应该叫作 message-error。通过包含模块名称可以清楚地表明这个类属于消息模块。常用的写法是使用两个连字符来表示修饰符,比如 message–error。修饰符的样式不需要重新定义整个模块,只需覆盖要改变的部分。

当模块需要有不同的外观或者表现的时候,就创建一个可以直接应用到指定元素的修饰符类。比如,写.dropdown–dark 而不是写成 .page-header .dropdown。通过这种方式,模块本身,并且只能是它本身可以决定自己的样式表现。其他模块不能进入别的模块内部去修改它。千万不要使用基于页面位置的后代选择器来修改模块。坚决遵守这个原则,就可以有效防止样式表变成一堆难以维护的代码。

多元素模块

应该避免使用基于通用标签类型的匹配,比如 div 和 span。

模块组合

关于类的第一条规则是类应该短小,第二条规则是还要更短小。

拆分模块

每个模块应只做一件事情。消息模块的职责是使消息提示醒目;媒体模块的职责是在一段文本中配置一张图片。我们可以简洁明了地概括它们的目标。有的模块是为了版面布局,有的是为了编写体例。当模块想要完成不止一件事的时候,我们应该考虑把它拆分成更小的模块。

创建模块之前应该先自问一下:“从更高的层面上看,这个模块的职责是什么?”如果不得不使用并(或者和)这个词来表述模块的职责,那可能正在描述多项职责。如果是的话,我们需要为每个职责分别定义模块。这是模块封装一个非常重要的原则,叫作单一职责原则(Single Responsibility Principle)。尽可能把多种功能分散到不同的模块中,这样每个模块就可以保持精炼、聚焦,并且容易理解。

模块命名

为模块命名是个很伤脑筋的事情。开发模块的时候我们可以用个临时的名称,但是最终完成之前一定要注意命名。这可能算是模块化 CSS 开发里最难的部分了。

模块的命名应该有意义,无论使用场景是什么。同时也要避免使用简单地描述视觉效果的名称。我们应该换一种思路,思考模块代表什么含义。这一般并不容易。

有些人强制用两个词来命名每个模块,这样就可以避免模块指代不明确,因为不知道什么时候会需要另一个新的板块模块。如果现有的板块模块命名比较明确,新的板块模块出现的时候再取名就会比较容易,不至于跟前一个混淆。为模块的变体类命名的时候,应该遵守同样的原则。使用大或小这样具有相对意义的词语来命名修饰符不是最佳方式,但也可以接受。

工具类

有时候,我们需要用一个类来对元素做一件简单明确的事,比如让文字居中、让元素左浮动,或者是清除浮动。这样的类被称为工具类(utility class)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.text-center {
text-align: center !important;
}

.float-left {
float: left;
}

.clearfix::before,
.clearfix::after {
content: " ";
display: table;
}

.clearfix::after {
clear: both;
}

.hidden {
display: none !important;
}

从某种意义上讲,工具类有点像小号的模块。工具类应该专注于某种功能,一般只声明一次。通常把这些工具类放在样式表的底部,模块代码的下面。

工具类是唯一应该使用 important 注释的地方。事实上,工具类应该优先使用它。这样的话,不管在哪里用到工具类,都可以生效。我敢肯定,任何时候为元素添加 text-center 类,都是想让文本居中,不想让其他样式覆盖它。用了 important 注释就可以确保这一点。

工具类的作用立竿见影。在页面上做点小事儿的时候不需要创建一个完整的模块,这种情况下可以用一个工具类来实现。但是不要滥用工具类。对于大部分网站,最多十几个工具类就够用了。

在大型团队里书写模块化样式,需要一些苛刻的约束条件来确保每个人遵守相同的约定。同时也需要采取一些措施来防止大家新建的模块名称出现冲突。为了解决这些问题,一些 Web 开发社区开始尝试模块化 CSS 的替代方案。一番探索后,他们转向了 JavaScript,最终发明了一种解决方案,被称为内联样式(inline styles)或者 CSS in JS。这种方案不再依赖类命名的口头约定,而是使用 JavaScript 来控制,要么生成独一无二的类名,要么使用 HTML 的 style 属性引入所有的样式。已经出现了不少具备这种功能的 JavaScript 库,其中比较流行的有 Aphrodite、Styled Components 和一个叫作 CSS Modules(容易引起误解)的库。绝大部分库绑定了一个 JavaScript 框架或者工具集,比如 WebPack。


CSS模块化
https://xuekeven.github.io/2025/08/02/CSS响应式设计/
作者
Keven
发布于
2025年8月2日
更新于
2025年8月2日
许可协议