CSS Modules 通过构建时重命名类名(如 Button_button__abc123)从物理层面杜绝全局样式冲突,其核心是生成哈希映射对象供 className 动态引用,而非直接写死类名;:global() 仅用于可控穿透,动画名等需显式引用。

因为 CSS 全局命名空间污染不是“可能出问题”,而是“必然出问题”——只要两个组件写了同名类,比如 .header 或 .button,样式就会互相覆盖,且调试时根本看不出是谁干的。CSS Modules 不靠人肉约定、不靠 BEM 长命名,而是用构建时重命名(如 Header_header__abc123)从物理层面切断冲突路径。
为什么直接写 .button 就会污染全局
CSS 选择器天生无作用域:浏览器只认类名字符串,不管它在哪个文件里定义。哪怕你把样式放在 Button.css 里,只要最终生成的 CSS 是 .button { ... },它就会匹配页面中所有 class="button" 的元素——包括其他组件、第三方库、甚至 HTML 模板里硬写的节点。
- 没有文件边界:CSS 文件之间不隔离,
@import或多个link会合并到同一全局样式表 - 加载顺序决定优先级:后加载的样式能覆盖前面的,但没人能保证打包顺序或 runtime 动态插入顺序
- DevTools 里看到的 class 名和源码一致 → 说明没启用模块化,风险已暴露
import styles from './Button.module.css' 这行代码到底做了什么
它不是“引入样式”,而是引入一个 JS 对象,键是原始类名,值是构建后唯一哈希类名:{ button: 'Button_button__kx8rA', disabled: 'Button_disabled__m3n4b' }。真正起作用的是这个映射关系,而不是 CSS 文件本身。
- 构建工具(Webpack/Vite)必须识别
.module.css后缀,并对其中的class和id选择器做重命名 -
className={styles.button}才能把哈希类名注入 DOM;写成className="button"就绕过整个机制 - 该对象不可枚举、不可遍历,也不能用
Object.keys(styles)动态取类名——编译期确定,运行时只提供已声明的 key
为什么 :global() 不是补丁,而是精确手术刀
:global() 的存在不是为了“退回到全局”,而是为了有控制地穿透模块边界——比如定制 Ant Design 的 .ant-select,但仅限于当前组件内生效。
立即学习“前端免费学习笔记(深入)”;
- 错误用法:
:global(.button) { ... }→ 直接污染全局,等于没用 CSS Modules - 正确用法:
.wrapper :global(.ant-select-selector) { border-color: #1890ff; }→ 样式只作用于.wrapper下的.ant-select-selector - 动画名、CSS 变量、
@keyframes默认不被模块化,必须显式用styles.animationName引用,否则动画失效
最易被忽略的点:CSS Modules 不处理 @keyframes 名字本身,只处理引用它的 animation 属性值;如果你在 CSS 里写 animation: spin 1s;,但 JS 中没通过 styles.spin 传给元素,那动画就跑不起来——哈希类名不会自动注入到 @keyframes 声明里。


















