styles.button__icon 返回 undefined 的根本原因是文件名与 Block 名不一致,如 Button.module.css 中未声明 .button__icon,或文件名大小写/命名不匹配,导致 CSS Modules 无法生成对应映射键。

直接用 styles.button__icon 是错的起点——CSS Modules 的类名映射依赖文件名作为 Block 根,而 BEM 的语义必须从文件名、类名、JSX 三者对齐开始,缺一不可。
为什么 styles.button__icon 会返回 undefined
常见错误不是写错了 CSS,而是文件名和 Block 名不一致。比如文件叫 Button.module.css,但里面写了 .btn__icon;或者文件是 button.module.css(小写),却在 JSX 中访问 styles.button__icon(首字母大写)。CSS Modules 会严格按文件名生成哈希前缀:Button.module.css → Button_button__abc123,但 button__icon 不在该文件中声明,styles.button__icon 就是 undefined。
-
Button.module.css中只允许出现以.button开头的类名(如.button、.button__icon、.button--primary) - 文件名必须 PascalCase,且与组件名完全一致;
button.module.css或MyButton.module.css都会导致映射断裂 - Element 必须用双下划线,
.button-icon是普通类,不参与 BEM 结构,也不会被styles.button__icon匹配
如何安全拼接 BEM 类名:别手写字符串,用 classnames + 封装函数
手动拼 {`button ${isDisabled ? 'button--disabled' : ''}`} 容易多空格、漏连字符、拼错 modifier 值,且 TypeScript 无法校验。正确路径是把 Block 名固化,再派生 Element 和 Modifier。
- 定义
bem工厂函数:const b = bem('button'),然后用b.e('icon')得到'button__icon',b.m('primary')得到'button--primary' - 传给
classnames的必须全是函数产出值,禁止混入字面量字符串:classnames(b(), { [b.m('disabled')]: disabled })✅,classnames('button', { 'button--disabled': disabled })❌ - 如果用了 CSS Modules,
b.e('icon')返回的是原始名,需再查styles对象:styles[b.e('icon')],而不是直接styles.button__icon(后者隐含硬编码风险)
为什么 postcss-bem-linter 不是可选插件
它是在构建阶段强制校验 BEM 合法性的底线工具。没有它,团队很容易写出 .button__icon--large--dark 这种嵌套修饰符,或在 Button.module.css 里偷偷引用 .modal__close,破坏 Block 边界。
立即学习“前端免费学习笔记(深入)”;
- 它能拦截
.button .icon(带空格的选择器)、.button-icon(缺少双下划线)、.button__content--loading(Element 下不该挂 Modifier)等反模式 - 配合 ESLint 插件
eslint-plugin-css-modules,可进一步限制 JSX 中只能使用当前模块声明过的 BEM 类名键 - 一旦启用,
Button.module.css中出现任何非button开头的类,构建就失败
BEM 的真正约束力不在命名规则本身,而在文件名、CSS 类名、JSX 中的字符串三者是否始终指向同一个 Block 实体——漏掉任意一环,哈希化就只是掩盖混乱,不是解决混乱。


















