bem + sass 解决结构漂移问题:class 名如 header__logo 表达语义归属而非 dom 嵌套,sass 嵌套仅提升书写效率,编译后仍为扁平 class,避免后代选择器脆弱性,且需统一 modifier 语义与命名边界。

直接用 .header .logo 这类后代选择器写样式,在现代组件开发里不是“写法不对”,而是根本不可靠——DOM 结构一变,样式就断;子组件复用时,class 名冲突或覆盖是常态。
为什么 BEM + Sass 嵌套不是炫技,而是解决结构漂移问题
BEM 的 header__logo 不表达视觉嵌套,只表达语义归属。哪怕 Logo 组件被 Portal 挂到 下,只要它逻辑上属于 Header,就该用这个 class。Sass 的 @include b('header') 只是生成扁平 class 名的辅助手段,不改变 DOM 依赖关系,也不引入运行时开销。
- 真正起作用的是 class 名本身,不是 Sass 编译过程;
@include e('logo')最终输出仍是header__logo,和手写没区别 - Sass 嵌套(如
.header { &__logo { ... } })只是书写便利,编译后仍是独立规则,不会产生.header .logo这种脆弱选择器 - 如果团队已用 PostCSS 插件(如
postcss-bem),Sass 就非必需;但 Sass 对开发者更友好,尤其在需要条件拼接 modifier 时(如header--dark__logo)
在 React/Vue 中传 class 名,谁该负责拼接 block 前缀
错误做法:子组件自己硬编码 const className = 'header__logo' 或拼 ${props.blockName}__logo。一旦父组件改名,所有子组件都要动,耦合度爆表。
- 推荐方式:父组件统一决定 block 上下文,通过
iconClassName="header__logo"这类明确 props 传入完整 class 名 - 次选方式:用
classNameprop 接收父级 class 名(如header),子组件只负责加__logo后缀 —— 但必须确保父组件传的是 block 名,不是带修饰符的全名(比如不能传header--fixed) - 避免 context 或全局配置自动注入 block 名,它会让子组件行为变得隐式且难调试
Sass 中用 @include b() 时容易漏掉的关键点
很多项目配了 sass-bem 或自定义 mixin,却没意识到这些工具只解决“怎么写”,不解决“怎么管”。
- 必须禁用
&在嵌套中意外生成后代选择器:例如.header { .logo { ... } }会输出.header .logo,完全违背 BEM 原则 - modifier 写法要统一:
header--large是合法 modifier,但header__logo--large和header--large__logo语义不同,前者表示 logo 自身变大,后者表示整个 header 处于 large 状态下的 logo 样式 —— 必须提前约定并文档化 - 不要为每个 element 都建独立文件(如
_logo.scss),BEM 的 block 是最小可复用单元,header.scss应包含所有header__*规则
最常被忽略的其实是命名边界的模糊性:比如 header__nav 里再塞一个 nav__item,这就偷偷引入了第二层 BEM 结构,实际应统一为 header__nav-item。BEM 不是递归的,它的层级感只存在于 class 名字符串里,而不是 DOM 或 Sass 文件结构中。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











