必须结合BEM与CSS Modules:BEM提供语义化命名结构,CSS Modules通过哈希实现样式隔离,二者缺一不可;文件名、类名、JSX引用须严格对齐,禁用SCSS嵌套生成BEM类名,动态类名须用clsx处理。

直接在 .module.css 文件里写 BEM 类名,但必须配合模块化导入方式和命名约束——否则类名再规范,运行时也会被哈希打散、失去可追溯性。
为什么不能只靠 BEM 命名,还得用 CSS Modules
BEM 本身不解决作用域问题:你写了 .card__title--large,另一个 Card.module.css 或第三方库照样能定义同名类,最终样式被覆盖。CSS Modules 的哈希编译(如 Card_card__title--large__abc123)才是实际隔离手段。但若不用 BEM 结构,哈希后只剩 Card_title__abc123,调试时根本看不出它属于哪个块、是元素还是修饰符。
- 纯 BEM → 可读性强,但协作中极易冲突
- 纯 CSS Modules → 隔离安全,但开发者工具里看到的类名语义断裂
- 二者结合 → 源码保留
.card__title--large,构建后变成唯一哈希,既可读又安全
文件名、类名、JSX 引用三者必须严格对齐
这是最容易漏掉、却最影响可维护性的硬约束。不对齐会导致 Ctrl+Click 跳转失效、IDE 无法识别引用、stylelint-selector-bem-pattern 校验失败。
- React 组件叫
Card.tsx,CSS 文件必须是Card.module.css(PascalCase,无下划线) -
Card.module.css里只允许出现以card开头的类名:.card、.card__header、.card--featured - JSX 中必须通过解构对象访问:
className={styles.card__header},不能写className="card__header" - 修饰符含双连字符(
--)的类名需加引号:className={styles['card--featured']}
禁止在 SCSS/Sass 中用 & 嵌套生成 BEM 类名
SCSS 的 & 是字符串拼接器,不是 BEM 语法检查器。一不留神就产出非法结构,比如 .card--compact__header(修饰符直接修饰元素,违反 BEM 规则)。
- 错误写法:
.card { &--compact { &__header { } } }→ 编译出.card--compact__header - 正确写法:
.card { &--compact &__header { } }→ 编译出.card--compact .card__header - 更稳妥的做法:禁用嵌套,所有类名平铺书写,靠注释分区(
/* ========================================================================== ELEMENTS ========================================================================== */) - 如果用 Sass,只允许
&__element和&--modifier这两种&用法,其余一律手写完整类名
动态类名必须用 clsx,禁用字符串拼接
手拼 className={`${base} ${base}--${size}`} 容易漏空格、重复类、顺序错乱,且 IDE 无法跳转到对应 CSS 规则。
- 推荐封装函数:
cnCard({ size: 'large', variant: 'featured' })返回['card', 'card--large', 'card--featured'] - 再交由
clsx合并:className={clsx(cnCard(props))} - 这样既能静态分析类名组合,也能让
stylelint-selector-bem-pattern在 CI 中校验每个字符串是否合规 - 避免在模板中出现
is-active这类裸状态类——应绑定块名,写成card.is-active或card__header.is-active
真正难的不是写对一个 .button__icon--rotated,而是在每次新增类名时,都确认它归属哪个 Block、是否该升格为独立组件、有没有在 JSX 和 CSS 文件里同步改名——BEM + CSS Modules 的价值,全系于这一念之差。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











