结论:样式污染源于全局类名冲突与加载顺序不可控,而非css引入即覆盖;需用.module.css后缀、styles对象引用类名、谨慎使用:global()、避免动态字符串拼接,并确保组件库css优先加载。

CSS Modules没启用,类名还是全局的
你在Button.css里写.btn,它就真叫.btn——所有组件只要也写了.btn,就会互相覆盖。React脚手架默认只把.module.css当作模块处理,.css文件走的是传统全局路径。
- 检查文件后缀:必须是
Button.module.css,不是Button.css - 检查导入方式:必须用
import styles from './Button.module.css',不能import './Button.module.css'(后者只是加载,不返回类名映射) - 检查JSX写法:必须用
className={styles.btn},不能写className="btn"(字符串字面量绕过哈希)
:global() 用错位置,反而放大污染
想覆盖组件库(比如Ant Design)内部样式时,很多人在.module.css里写:global(.ant-btn) { color: red },但没意识到:这个规则依然会全局生效,且可能被其他:global()覆盖或干扰。
-
:global()里的选择器不经过哈希,等于“开后门”——它应该只用于真正需要穿透的场景,比如.map :global(.am-navbar-title)这种带父级限定的写法 - 避免单独写
:global(.ant-btn),优先用更具体的选择器提升特异性,例如.my-page :global(.ant-btn-primary) - Vite用户注意:
:global在style标签里(如Vue)和.module.css里行为一致,但不要混用scoped和:global逻辑
动态拼接 className 漏掉 styles 对象
写className={`btn ${isPrimary ? 'primary' : ''}`}看着没问题,但'primary'是纯字符串,CSS Modules根本不管它,编译后找不到对应哈希类名。
- 所有类名都要从
styles对象取:className={`${styles.btn} ${isPrimary ? styles.primary : ''}`} - 条件多时别手写拼接,用
clsx:className={clsx(styles.btn, isPrimary && styles.primary, isDisabled && styles.disabled)} - 服务端渲染(Next.js/RSC)下
styles对象为空对象,此时clsx返回空字符串——要配合useEffect或dynamic做客户端补救
组件库样式加载顺序被HMR打乱
本地开发时(npm start),Bootstrap或Ant Design的CSS可能通过CDN异步加载,而你的.module.css同步注入,结果反而是组件库样式后生效,把你写的:global()盖掉了。
- 不要依赖HTML中
<link>顺序,改用构建时引入:import 'antd/dist/reset.css'写在index.tsx最顶部 - 确保组件库CSS在自定义
:global样式之前执行,否则加!important都可能失效(因为特异性计算发生在解析阶段) - Chrome DevTools里看
<style></style>标签顺序,如果发现你的样式块排在组件库后面,说明构建配置或导入顺序有问题
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











