Scoped CSS与BEM是分工协作关系:前者管作用域边界,后者管语义归属;合用可防污染、提可读,但Block名不对齐、动态类名未声明、穿透无容器三处易翻车。

Scoped CSS 和 BEM 不是简单叠加关系,而是分工明确的协作:scoped 负责「作用域边界」,BEM 负责「语义归属」;合用能防污染、提可读,但 Block 名不对齐、动态类名未声明、穿透无容器这三处最容易翻车。
Block 名必须严格对齐 .vue 文件名
比如 UserCard.vue 的 Block 名只能是 user-card,不是 card 或 userCard。DevTools 里搜不到样式、协作时类名归属混乱,往往就卡在这一步。
- 文件名
SearchBar.vue→ Block 名必须是search-bar;OrderSummaryItem.vue→ 必须是order-summary-item,不能缩写为order-item - 模板中所有类名必须以该 Block 名开头:
user-card__header✅,__header❌(孤儿元素),card__header❌(错配 Block) - 常见错误是把语义当命名,比如在
OrderSummaryItem.vue里写order-item__summary,实际应为order-summary-item__summary——冗长但可查、可定位
SCSS 嵌套时 & 不能替代 BEM 规则
& 是 Sass 拼接工具,不是 BEM 语法检查器。一不留神就会产出非法选择器,比如 .user-card--compact__header,它违反 BEM(修饰符不能直接修饰元素)。
- 正确写法:
.user-card { &--compact &__header { ... } }→ 编译为.user-card--compact .user-card__header✅ - 错误写法:
.user-card { &--compact { &__header { ... } } }→ 编译为.user-card--compact__header❌ - Element 修饰符合法:
.user-card { &__header { &--large { ... } } }→.user-card__header--large✅ - 但多个修饰符连写不行:
&--large &--focused会编译成--large--focused,应拆成两个独立 class
动态类名必须静态声明才能保 scoped 生效
Vue 的 scoped 机制依赖静态类名分析。如果用字符串拼接生成 class,比如 :class="`user-card__avatar ${isLarge ? 'user-card__avatar--large' : ''}`",scoped 属性不会自动加到运行时生成的类上。
审查 Vue 管理后台的变更请求(Vue 2 + Element UI,Vue 3 + Element Plus)。重点关注最小化变更实现、上线风险检查以及回归检查清单生成。
立即学习“前端免费学习笔记(深入)”;
- 必须提前在模板中显式写出所有可能的类名:
user-card__avatar、user-card__avatar--large等,哪怕只在部分条件下使用 - 推荐用
clsx封装函数统一管理:cnAvatar({ size: 'large', shape: 'square' })返回数组,再由clsx合并,IDE 可跳转、重构不漏、stylelint 可校验 - 避免手写
${base}--${mod}拼接:易漏空格、顺序错乱、source map 断链
穿透第三方组件必须用 BEM 容器限定范围
别在 <style scoped></style> 里直接写 ::deep(.el-button) —— 它会匹配当前组件内所有 el-button,不管来自哪个业务上下文。
- 正确做法:模板中用 BEM 容器包裹,比如
<div class="article-form__submit-btn"><el-button>提交</el-button></div> - 样式中仅穿透该容器下的目标:
.article-form__submit-btn :deep(.el-button) { height: 36px; } - 这样改的只是「文章表单里的提交按钮」,不会误动「用户设置页的保存按钮」;未来删样式,直接删掉
article-form__submit-btn上下文即可
BEM 和 scoped 协作的关键,不在语法多漂亮,而在 Block 名是否可追溯、动态类是否被静态覆盖、穿透是否被容器约束——这些地方一旦松动,整个隔离体系就从根上失效。

















