bem类名强制使用双下划线__和双破折号--是为解决css层叠冲突与复用性问题:__明确元素归属(如card__title仅属card),--专标状态变体(如button--large),全小写中划线命名保障团队协作一致性。

为什么BEM类名里要加双下划线和双破折号
BEM的block__element和block--modifier不是随意定的命名习惯,而是为解决CSS层叠和复用冲突而设计的硬性分隔规则。不按这个写,button__icon可能被.icon全局样式意外覆盖,button--large也可能因权重不够输给了.button:hover。
- 双下划线
__明确标识“属于谁”——card__title永远是card的子元素,不会和独立title组件混淆 - 双破折号
--专用于状态/变体,语义上区别于结构,也避免和JS钩子类(如js-toggle)命名重叠 - 所有类名必须全小写+中划线,
CardTitle或cardTitle都不合法——BEM不处理大小写,CSS选择器本身不区分,但团队协作时大小写混用会直接导致查找失效
怎么让BEM真正支持主题切换和动态加载
纯BEM类名本身不带逻辑,主题切换靠的是组合使用:基础块类 + 主题修饰符 + CSS自定义属性。比如theme--dark本身不写颜色,只在:root里定义--color-bg: #1a1a1a,再由card__body用background: var(--color-bg)读取。
- 禁止在
card--theme-dark里直接写background: #1a1a1a——这样无法被CSS变量覆盖,也无法做运行时主题热替换 - 动态加载组件时,确保修饰符类名和对应CSS已就绪;
import('./Button.module.css')返回的类名必须映射到BEM约定,不能依赖Webpack默认生成的Button_button__abc123 - 服务端渲染需同步注入主题CSS变量,否则首屏闪动——BEM不解决SSR水合问题,但命名规范能让你一眼看出哪部分该由JS接管、哪部分该由CSS兜底
React里用BEM怎么避免className拼接灾难
手写className={`${base} ${base}__${elem} ${base}--${mod}`}很快失控,尤其嵌套多层或条件多时。关键不是“要不要用工具”,而是“工具是否保留BEM语义可读性”。
- 别用
clsx简单堆砌:clsx('btn', 'btn__label', isPrimary && 'btn--primary')——btn__label脱离了父块上下文,无法定位来源 - 推荐封装一个
bem函数:bem('button')('label', { primary: true }),输出"button button__label button--primary",保持块名源头唯一 - 组件内所有BEM类名必须从同一个
blockName派生,禁止出现button__icon和icon并存——后者意味着你其实漏抽了一个独立iconblock
哪些情况BEM反而会让维护更糟
BEM不是银弹。当组件结构极不稳定、或UI极度依赖运行时计算(比如拖拽布局、画布编辑器),强行套BEM会导致类名爆炸、重构成本翻倍。
- 表单控件组合(
form-field__input+form-field__error+form-field__label)看似合理,但一旦需求变成“错误提示显示在右侧”,你就得新增form-field__error--right,而不是改布局逻辑 - 图标字体或SVG sprite场景下,
icon--home这种写法容易和语义化图标(icon__home)混淆——此时更适合用功能前缀:ui-icon--home+data-icon="home" - 第三方UI库(如Ant Design)已内置BEM类名,直接覆盖其
.ant-btn__content风险极高;更稳妥的是用其提供的classNameprop透传,或用CSS-in-JS隔离作用域
真正难的从来不是写对BEM语法,而是每次加一个__或--之前,得想清楚这个东西到底算“结构的一部分”,还是“可插拔的皮肤”,或者“根本就该是个独立block”。没想清就先写,后面删比改还费劲。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











