根本原因是文件名、类名、JSX访问键三者未对齐:文件名必须PascalCase且与组件名一致(如Button.tsx ↔ Button.module.css),类名须以.block__element形式声明,JSX中必须用styles['block__element']访问,不可直接styles.block__element。

styles.button__icon 返回 undefined 怎么办
根本原因不是 CSS 写错了,而是文件名、类名、JSX 中访问方式三者没对齐。CSS Modules 用文件名生成哈希前缀,Button.module.css → Button_button__xxx;如果文件叫 button.module.css 或 MyButton.module.css,styles.button__icon 永远是 undefined。
- 文件名必须 PascalCase,且与组件名完全一致(如
Card.tsx↔Card.module.css) - CSS 文件中只允许出现以
.card开头的类名:.card、.card__header、.card--fluid - JSX 中必须用字符串键访问:
styles['card__header']✅,styles.card__header❌(语法非法) - 大小写敏感:
styles['Card__header']和styles['card__header']是两个不同键
手拼 className 容易出错的典型场景
硬编码字符串看似快,实际在 CI 或多人协作中极易挂掉:空格漏了、连字符少一个、modifier 值拼错、null 被转成字符串 "undefined"——这些都不会报错,但样式静默失效。
- ❌ 危险写法:
className={`button ${isDisabled ? 'button--disabled' : ''}`}(可能多出空格或空字符串) - ❌ 更危险:
className={styles.button + ' ' + (isDisabled ? 'button--disabled' : '')}(绕过 CSS Modules 校验) - ✅ 推荐用
clsx+ BEM 工厂函数:clsx(styles.button, { [styles['button--disabled']]: isDisabled }) - 工厂函数可固化 Block 名:
const b = bem('button'),再用b.e('icon')生成button__icon,最后查styles[b.e('icon')]
postcss-bem-linter 为什么不是可选插件
它不是锦上添花,而是防止 BEM 结构滑坡的底线工具。没有它,团队很快会写出 .button__icon--large--dark 这种嵌套修饰符,或在 Button.module.css 里偷偷引用 .modal__close,破坏 Block 边界。
- 拦截
.button .icon(带空格的选择器,破坏封装) - 拦截
.button-icon(缺少双下划线,不参与 BEM 映射) - 拦截
.button__content--loading(Element 下不该挂 Modifier) - 配合
stylelint-selector-bem-pattern限定 block 白名单,比如只允许['button', 'card']
BEM 类名在 HTML 里变长,怎么控制体积
CSS Modules 不自动压缩类名长度,card__title--large 编译后还是完整字符串。真正缓解“臃肿”的,是模块化隔离带来的语义收口能力——你不再需要靠堆 modifier 防冲突,从而能收敛组合爆炸。
立即学习“前端免费学习笔记(深入)”;
- Modifier 必须收口:JS 层只暴露
size、theme、state等有限字段,而非把所有组合都塞进 CSS - 构建时可设
localIdentName=[name]__[local]___[hash:base64:5],保留可读性又缩短长度 - 禁止在 JSX 中混用字面量字符串和
styles对象:clsx('button', styles['button--primary'])❌,clsx(styles.button, styles['button--primary'])✅ - 最易忽略的一点:BEM 不是给机器看的,每次写
className前该问一句——“这个样式属于谁?它会不会在别的上下文里意外生效?”


















