直接拼字符串写 classname 会因漏空格、拼错 modifier、传入 undefined 及绕过 css modules 哈希校验导致样式失效;必须用 bem 工厂函数固化 block 名,配合 typescript 泛型约束 modifier,并严格对齐文件名、css 类名与 styles['xxx'] 字符串。

为什么直接拼字符串会失效
写 className={`button ${isActive ? 'button--active' : ''}`} 看似简单,实际踩三个坑:漏空格(button${isActive ? '--active' : ''})、拼错 modifier(button--prmiary)、传入 undefined 导致类名变成 button undefined。更关键的是,这种写法绕过 CSS Modules 的哈希校验,样式静默失效,DevTools 里看到的类名和源码对不上。
必须用 bem 工厂函数固化 Block 名
核心是把 Block 名(如 button)封死,再派生 Element 和 Modifier,所有类名路径可控:
const buttonBem = bem('button');
buttonBem(); // 'button'
buttonBem.e('icon'); // 'button__icon'
buttonBem.m('primary'); // 'button--primary'
buttonBem.e('icon').m('loading'); // 'button__icon--loading'
- 不能手写
'button__icon',必须通过函数生成,否则 TypeScript 无法约束、拼写错误不报错 - Modifier 值建议加 TypeScript 泛型,比如
type ButtonModifier = 'primary' | 'secondary' - Element 和 Modifier 不能混用层级:
button__icon--primary合法,button--primary__icon语法无效
CSS Modules 下必须二次映射到 styles 对象
文件叫 Button.module.css,里面写 .button__icon,编译后真实类名是 Button_button__icon___abc123,但 buttonBem.e('icon') 只返回原始名 button__icon。所以 JSX 中必须查 styles:
className={cn(
styles[buttonBem()],
{ [styles[buttonBem.m('disabled')]]: disabled },
{ [styles[buttonBem.e('icon')]]: hasIcon }
)}
-
styles.button__icon是错的——点号访问非法字符,且大小写/连字符错一位就undefined - 文件名必须 PascalCase 且与组件名一致:
Button.tsx↔Button.module.css,否则styles['button__icon']永远为空 - 类名必须以
.button开头,写成.btn__icon或.Button__icon都不会被映射
为什么 postcss-bem-linter 不是可选插件
它在构建阶段强制拦截反模式,比如:.button .icon(带空格)、.button-icon(缺双下划线)、.button__content--loading(Element 下挂 Modifier)。没有它,两周内团队就会出现 button--primary、btn-primary、ui-button--active 并存,而没人知道哪个该删。
真正容易被忽略的不是怎么写,而是三者对齐:文件名、CSS 类名、JSX 中 styles['xxx'] 的字符串——差一个字母或大小写,就是 undefined,且没有任何运行时提示。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











