css modules文件名必须与bem块名严格一致(如card组件对应card.module.css),类名须以.block开头且禁用多block混写;须用bem工厂函数生成类名并配合classnames,modifier须为受控枚举值;postcss-bem-linter是强制校验bem结构的底线工具。

React组件中CSS Modules文件名必须与BEM块名完全一致
文件名就是Block根,错一个字母或大小写,styles对象里就找不到对应类名。比如组件叫Card,CSS文件必须是Card.module.css,里面只能写.card、.card__header、.card--loading这类以.card开头的类——不能写.ui-card或.product-card,否则styles['card__header']返回undefined,且控制台不报错。
常见错误现象:console.log(styles)输出里没有card__footer,但CSS里明明写了;或者styles.card__footer是undefined,但styles['card__footer']能取到——说明文件名大小写不匹配(如文件是card.module.css,JS里却按Card访问)。
- 验证方法:打开编译后生成的CSS文件,看类名前缀是不是
Card_开头 - 开发期建议配置
localIdentName=[name]__[local]___[hash:base64:5],调试时一眼看出来源 - 不允许在同一个CSS文件里混用多个Block命名,比如
.card和.button不能共存
用bem工厂函数生成类名,别手写字符串
手写`card ${isHovered ? 'card--hovered' : ''}`有三个硬伤:漏空格、拼错连字符(比如card-hovered)、isHovered值为null时留下多余空格。更关键的是,它绕过类型检查和BEM结构约束。
正确做法是封装一个绑定Block名的生成器:
const bem = (block) => ({
e: (el) => `${block}__${el}`,
m: (mod) => `${block}--${mod}`
});
const cardBem = bem('card');
然后统一交给classnames处理:
import cn from 'classnames';
className={cn(
styles.card,
cardBem.m('loading'),
{ [styles[cardBem.e('footer')]]: hasFooter }
)}
- 所有传给
classnames的值必须来自cardBem,禁止混入字面量字符串(如'card--disabled') -
cardBem.e('footer')返回'card__footer',再通过styles[...]查真实哈希名,而不是直接写styles.card__footer(硬编码风险) - 每个组件内只调一次
bem('card'),返回的e/m是稳定引用,无重渲染开销
Modifier必须表达状态,且值要受控
BEM的--modifier不是视觉描述词,而是可枚举的状态标识。写cn(styles.card, `card--${theme}`)很危险——theme若来自props且未校验,可能注入非法类名(如card--dark;alert(1)),或拼出card--undefined。
安全做法是把修饰符值收敛到有限集合:
const VALID_SIZES = { small: true, large: true } as const;
type CardSize = keyof typeof VALID_SIZES;
// 使用时
const isLarge = size === 'large';
className={cn(styles.card, { [styles[cardBem.m('large')]]: isLarge })}
- 避免
card__content--loading这种反模式:Modifier应挂在Block上(card--loading),元素内部状态用独立类或JS控制显示 - 禁止嵌套修饰符,如
card--size-large--theme-dark,应拆成card--size-large card--theme-dark - 不要用位置词做Modifier,
card--top❌,改用card--featured或card--priority✅
postcss-bem-linter不是可选插件,是结构底线
没它,团队很快会写出破坏BEM边界的代码:.card .footer(含空格选择器)、.card-icon(缺双下划线)、.card__header--large(Modifier挂Element上)。这些不会报错,但让BEM语义失效,协作成本飙升。
它在构建阶段强制拦截非法写法,是防止结构滑坡的唯一自动化防线:
- 必须校验
.card__header是否真在Card.module.css里声明 - 拒绝
.card__header--loading这种Element下挂Modifier的写法 - 禁止跨Block引用,如
Card.module.css里出现.modal__close
真正难的不是写对单个类名,而是在多人长期迭代中守住Block边界——这靠人盯不住,只能靠工具链卡死。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











