想在 CSS Modules 项目中稳定保住代码可读性,关键就在于几处命名必须严格一一对应:Button.tsx 对应 Button.module.css,样式文件中只保留以 .button 开头的类名,JSX 里则通过 styles['button__icon'] 这样的方式访问;另外,注释要做好分区管理,禁止嵌套,配合 clsx+BEM 工厂 使用,再交给 postcss-bem-linter 做校验,这套 CSS Modules 与 BEM 命名规范基本就完整了。

启用 CSS Modules 之后,.button__icon 会被编译成 _button__icon_abc123,机器能够识别,但人阅读起来并不直观——想保留 BEM 命名的可读性,关键不是“阻止它哈希化”,而是让原始 BEM 类名在整个开发链路中始终可追溯、可定位、可协作。
文件名 + 类名 + JSX 访问键必须严格对齐
这是在 CSS Modules 中最容易踩坑的地方,也是唯一能确保 styles['button__icon'] 不会变成 undefined 的前提:
- React 组件命名为
Button.tsx,对应的 CSS 文件就必须是Button.module.css(PascalCase,无下划线、无前缀) Button.module.css中只允许出现以.button开头的类:✅.button、.button__icon、.button--primary;❌.btn__icon、.Button__icon、.button-icon- JSX 中必须使用字符串键访问:
styles['button__icon']✅,styles.button__icon❌(语法错误),styles['Button__icon']❌(大小写不匹配) - 验证方式也很直接:
console.log(styles)查看输出对象里是否存在button__icon这个 key;再打开 DevTools 检查编译后的类名是否以Button_button__开头
用注释分区让 .module.css 文件一眼可读
BEM 类名一旦堆在一起,就很容易失去结构层次感。添加注释并不是“装饰”,而是为编辑器和开发者提供清晰的折叠锚点:
- 每个文件按
BLOCK/ELEMENTS/MODIFIERS三段划分,使用统一格式注释:/* ========================================================================== BLOCK: button ========================================================================== */ - 每段之间空一行,区块内类名保持平铺展开(禁止使用 Sass 嵌套生成,否则注释分区会失去意义)
- Element 类只放在
ELEMENTS区,Modifier 类只放在MODIFIERS区——例如.button__label--highlighted必须归入 MODIFIERS,不能混进 ELEMENTS - 可在 VS Code 或 WebStorm 中配置按
/* ===折叠代码,Ctrl+F 搜索BLOCK:就能快速跳到模块入口
动态组合类名时别绕过 styles 对象
手写字符串拼接 `button ${isActive ? 'button--active' : ''}` 看起来省事,实际上等于主动放弃了 CSS Modules 的核心优势:
- 字符串中的
button--active不经过styles映射,构建后可能根本没有被正确引入,最终导致样式静默失效 - TypeScript 无法校验这类字符串拼写,像
button--primar这样的错误不会报错,只会在页面上表现为样式缺失 - 更推荐使用
clsx+ BEM 工厂函数:const b = bem('button'),然后clsx(styles.button, styles[b.m('primary')], { [styles[b.e('icon')]]: hasIcon }) - 工厂函数返回的是原始 BEM 字符串(例如
'button__icon'),再通过styles[...]查表映射,既能保留语义化命名,也能保留哈希校验能力
postcss-bem-linter 是底线,不是加分项
它可以在构建阶段拦截所有破坏 BEM 结构的写法,比单纯依赖人工 Code Review 更稳定、更可靠:
- 会直接报错:
.button .icon(后代选择器)、.button-icon(缺少双下划线)、.button__content--loading(Element 下挂 Modifier) - 不会报错但会给出警告:
.button--primary--large(多个 modifier 是否正交仍需人工确认) - 配合
stylelint-selector-bem-pattern使用,可在保存时就得到提示,不必等到构建失败才发现问题 - 小团队可以先用 ESLint 插件起步,但一旦组件数量 > 50,最好尽快接入
postcss-bem-linter,否则命名结构一旦滑坡,后期很难逆转
真正最容易被忽略的,并不是怎么写出 .button__icon,而是忘了 BEM 的核心价值并不只在 CSS 本身——而在文件命名、目录结构、JSX 类名访问这三者之间的映射一致性。只要其中任何一环断开,可读性就会立刻塌掉一半。
