中大型项目必须同时用bem和css modules:bem解决语义与协作边界,css modules实现作用域隔离;单独用bem无法防止全局类名冲突,单独用css modules则丢失可读性与上下文。

中大型项目不是“选BEM还是CSS Modules”,而是必须同时用——BEM管语义和协作边界,CSS Modules管作用域隔离,缺一不可。
为什么单独用BEM在中大型项目里会崩
BEM只是一套命名约定,不改变CSS全局作用域本质。哪怕你写.product-card__header--large,另一个团队成员或第三方UI库照样能定义同名类,最终在DOM里发生覆盖。调试时看到这个类名,你无法判断它来自哪个文件、是否已被覆盖;搜索替换可能误伤其他模块;多人并行开发时,“我按BEM写,他还在section .title里嵌套”这种混用极难靠人盯住。
- 常见错误现象:
margin越调越大、改一个按钮颜色结果所有button都变了、开发者工具里看到的类名找不到定义位置 - 性能影响小,但协作成本高:BEM类名越写越长(如
.checkout-form__submit-wrapper--loading-disabled),是人力边界被反复试探的结果,不是设计缺陷 - 真正难的不是写出
block__element--modifier,而是让新人加一个tooltip时,本能地新建/components/tooltip/,而不是往modal.css里塞.modal__tooltip
为什么单独用CSS Modules会让团队失去上下文
CSS Modules把.button编译成Button_button__abc123,彻底防冲突,但代价是原始语义丢失。HTML里class="Button_button__x7f9a"无法靠眼识别组件归属;服务端渲染或调试时,原始类名不可见,排查样式来源得反复跳转;更关键的是,styles.primary、styles.danger这类键名缺乏上下文——没人知道primary是主操作按钮、表单提交按钮,还是卡片标题。
- 常见错误现象:
styles.button__icon总是undefined,其实是因为文件名Button.module.css、类名.button__icon、JSX访问键styles['button__icon']三者没对齐 - localIdentName别配太“可读”:用
[name]__[local]___[hash:base64:5],编译后是Button__button___abc123,一眼能定位到文件和类名 - TypeScript项目必须加
declare module '*.module.css',否则import styles from './Button.module.css'直接报类型错误
怎么把BEM和CSS Modules真正叠在一起用
在.module.css文件里继续写BEM风格类名:.button、.button__icon、.button--loading;导入后通过styles['button--loading']访问(注意双连字符需引号);构建时由CSS Modules自动加哈希,既保语义又防污染。
- 文件名必须PascalCase且与组件名一致:
ProductCard.module.css→import styles from './ProductCard.module.css' - 禁止在JS里拼接字符串类名:
{`button ${isActive ? 'button--active' : ''}`}绕过校验、易拼错、无类型保护 - 推荐封装BEM工厂函数:
const b = bem('button'),再用classnames(styles.button, styles[b.m('active')]),所有产出可控可测 - SCSS里只允许
&__element和&--modifier两种嵌套,禁用& a或& > span——BEM的约束力来自构建时校验,不是命名习惯
最容易被忽略的一点:BEM的__和--是人为契约,CSS Modules的哈希是机器兜底。项目越早统一这两层规则,后期动一个按钮样式却意外影响导航栏的概率就越低。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











