直接写 .header-nav 会埋下样式冲突隐患,因为它未声明所属 Block 也未区分 Element 或独立模块,缺乏归属契约;BEM 要求类名明确体现层级关系,如 .header__nav 表示该导航元素从属于 header 块、不可脱离复用。

为什么直接写 .header-nav 会埋下样式冲突隐患
因为 .header-nav 看似语义清晰,实则既没声明所属 Block,也没区分是 Element 还是独立模块——它可能被误当成 Block 复用,也可能被其他页面的 .nav 规则意外覆盖。BEM 要求每个类名自带“归属契约”,比如 .header__nav 明确表示:这是 header 块里的一个导航元素,不 standalone,不可跨块复用。
常见错误包括:
- 把视觉分组当 Block,比如
.section-2或.page-header——它们没有独立功能,只是临时容器,无法沉淀为可复用单元 - 在 SCSS 中写
.block { &__item { &__icon {} } },生成.block__item__icon,违反 BEM 单层 Element 原则 - 用
.button--red这类描述性修饰符,换品牌色时要全局搜索替换,而.button--primary是语义化、可枚举的状态
.card__title 和 .card__card-title 哪个符合 BEM
.card__title 正确;.card__card-title 是典型冗余命名。Element 名称只用名词,不回溯父级语义——title 本身已足够表达用途,加 card- 前缀反而模糊了职责边界。
判断依据很直接:
立即学习“前端免费学习笔记(深入)”;
- 这个元素能否脱离
card块单独存在?不能 → 它是 Element,命名用__title - 如果它在用户列表、弹窗、侧边栏都出现且表现一致,那它大概率该抽成独立 Block(如
.text-heading),而不是塞进多个父块里重复定义 - SCSS 中必须用
&__title,禁止写.card__title .subtitle这类后代选择器——后者等于放弃 BEM 的作用域隔离能力
React + CSS Modules 下,className={styles['button__icon']} 为什么是危险操作
因为 CSS Modules 的局部作用域只保住了类名不泄漏,但没保住 BEM 的语义层级。你手动取 button__icon,就绕过了组件对 Block 边界的封装,后续若把 icon 抽成独立 .icon Block,所有硬编码的地方都会断。
安全做法是收敛入口:
- 组件顶部定义
const BLOCK = 'button',所有类名基于它派生:${BLOCK}__icon、${BLOCK}--loading - 对外只暴露 Block 类名,Modifier 和 Element 由组件内部组合:
<button icon="left"></button>渲染出button button--icon-left button__icon - 禁用字符串拼接:
className={`button ${isActive ? 'button--active' : ''}`必须替换成clsx('button', { 'button--active': isActive })
媒体查询和响应式类名该放在哪一级
BEM 明确禁止用 Modifier 表达断点,比如 .button--mobile 是错的。响应式逻辑不属于状态,而是视口约束,必须收口到统一断点文件中,用媒体查询包裹真实规则。
正确结构是:
- 所有 Block 的响应式样式写在
src/styles/breakpoints.scss里,按$bp-sm、$bp-md等变量组织 - Block 文件内只写基础样式,需要适配时用
@include media-breakpoint-up(sm) { ... } - 第三方 UI 库样式必须套一层 wrapper,比如
<div classname="third-party-wrapper"><datepicker></datepicker></div>,再通过.third-party-wrapper .react-datepicker局部重置,避免污染全局
最易被忽略的一点:BEM 不是命名游戏,它是协作契约。没有 ESLint 校验 className 字符串是否匹配当前 Block 名,没有目录强制要求 src/components/Button/Button.module.scss,再规范的命名也会三个月后变成 .header__logo--large--dark--v2。


















