bem是模块化方案的底层语义骨架,确保样式归属清晰、边界可校验、协作无歧义;它强制类名与模块强绑定,杜绝命名混沌,使样式具备业务语义、机器可检、跨团队可理解。

因为BEM不是“一种模块化方案”,而是让所有模块化方案(CSS Modules、CSS-in-JS、scoped style)能真正落地的底层语义骨架——没有它,模块化只解决类名不冲突,却留着“谁该管这个样式”的混沌。
为什么 CSS Modules 生成哈希类名后,仍要写 .user-card__avatar 而非 .avatar_abc123
CSS Modules 的 styles.avatar 编译后是随机字符串,但开发者无法从字符串反推语义。而 .user-card__avatar 在源码里就声明了归属和结构:它属于 user-card 模块,是其直属头像元素,不是通用工具类,也不是某个父级下的任意 avatar。
- 构建产物中若出现
.user-card__avatar和.message-list__avatar,哪怕共用同一份 CSS 文件,也天然隔离 - VS Code 搜索
user-card__avatar可直接定位到/components/user-card/目录下唯一文件,不用靠import链路跳转 - 后端模板工程师或 QA 查 DOM 时,看到
user-card__avatar--xs就知道这是用户卡片模块的小尺寸头像,不需要打开 JS 或构建配置
为什么 scoped style 不能替代 BEM 的语义约束
<style scoped></style> 给选择器加 [data-v-xxx] 属性,只防外溢,不管内部怎么命名。你在同一个 .vue 文件里写十个 .title,scoped 不会报错,但协作时没人知道哪个 title 是卡片标题、哪个是弹窗标题、哪个是侧边栏标题。
- scoped 允许你写
.card .title,但 BEM 强制你写.card__title,后者不依赖 DOM 层级,换行、加 wrapper、抽离子组件都不破 - 第三方组件(如
<el-button></el-button>)不受 scoped 保护,必须靠 BEM 容器包裹 +::v-deep才能安全定制,否则样式可能漏掉或污染全局 - 父子组件都用 scoped 时,子组件根节点带两个
data-v-属性,某些 SSR 场景下会触发父级样式意外匹配
为什么 BEM 类名必须和目录/文件名强对齐
这不是为了“整齐好看”,而是把模块边界从文档约定变成机器可校验的事实。一个 user-card.css 文件里出现 .product-card__price,说明模块已失焦——这种错误人眼难查,但 stylelint-selector-bem-pattern 可立刻拦截。
- 规则配置为
{"componentName": "user-card"},就能确保该文件只允许.user-card、.user-card__avatar、.user-card--loading - CI 中跑
npx stylelint "**/*.css",失败即阻断合并,比 Code Review 更早卡住边界泄漏 - 微前端场景下,子应用打包时若误引入
checkout-form.css里的.user-card__badge,构建工具能靠路径校验直接报错,而不是等线上样式炸了才暴露
BEM 最容易被忽略的硬性边界:Element 不得跨 Block 存在
把 .user-card__avatar 放进 message-list 容器里,看似只是复用,实则破坏 BEM 契约——__avatar 的语义只在 user-card 上下文成立,脱离后它既不是用户卡片的一部分,也不具备该模块定义的状态逻辑(比如 user-card__avatar--loading 在消息列表里毫无意义)。
- 正确做法是:若消息列表需要头像,应定义独立 Block
.message-avatar,或复用user-card组件本身 - SCSS 中写
.user-card { &__avatar { } }安全;但写.message-list { & .user-card__avatar { } }就编译出带空格的选择器,破坏扁平性且无法被 BEM 工具链识别 - 审查元素时看到
.user-card__avatar出现在非.user-card下,第一反应不是“样式生效了”,而是“DOM 结构或类名使用有误”
真正难的不是拼出 __ 和 --,而是在写每个类名前,确认它是否还带着明确的业务归属、是否脱离当前模块就失去语义、是否经得起 DOM 重构和跨团队协作的拷问。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











