按 block 名建一级目录,如 /components/button/;每个 block 目录仅含核心样式、js 行为及可选类型导出文件;禁止泛目录、嵌套子目录、跨 block 样式 import 和空格选择器;推荐 @layer 分层与命名空间隔离。

按 block 名建一级目录,不按功能或层级
每个 UI 组件对应一个独立的 block,目录名就是 block 名,小写、短横线分隔,比如 /components/button/、/components/card/。不要建 /components/layout/ 或 /components/elements/ 这类泛目录——BEM 的组织逻辑是“这个东西能不能被单独引用、测试、复用”,不是“它在页面里出现在哪一层”。
常见错误:
- 把
card__button单独拆成/components/card-button/:它只是card的一个element,没有独立生命周期 - 在
/components/card/下再建/card-header/子目录:header 不是独立 block,不该有自己文件夹 - 把所有修饰符(
--dark、--large)统一收进/modifiers/:破坏了 block 的封装性,改一个修饰符得跳三个文件
每个 block 目录只放三类文件:核心样式、JS 行为、可选子组件引用
/components/button/ 下应有且仅有:
-
button.scss:只写.button、.button__icon、.button--primary等,不出现其他 block 名 -
button.js:只处理.button自身交互,比如点击反馈、加载状态切换,不操作.tooltip或.icon - (可选)
index.js或types.ts:导出组件类型或注册逻辑,不放样式
如果 button 里用了 icon,就直接 @import "@/components/icon/icon.scss",而不是把 icon 文件拷进 button 目录——否则改 icon 样式就得同步修 N 个副本。
元素(__)和修饰符(--)不单独建文件,但可用 @layer 或 @at-root 隔离作用域
Sass 1.3.0+ 支持 @layer,推荐按语义分层,而非按文件物理拆分:
@layer button.base { .button { … } }@layer button.element { .button__icon { … } }@layer button.modifier { .button--loading { … } }
这样编译后 CSS 仍扁平,但源码可读性强;若用旧版 Sass,可用 @at-root 避免嵌套污染:
.button {
@at-root .button__icon { margin-right: 0.25em; }
@at-root .button--loading { opacity: 0.6; pointer-events: none; }
}
禁用写法:.button { &__icon { … } } —— 它会生成 .button .button__icon(带空格),违反 BEM 扁平原则。
跨 block 复用时,靠命名空间隔离,不靠文件路径耦合
多个团队共用同一套组件库时,容易出现类名冲突。解决方案是在 Sass 变量中定义命名空间:
-
$namespace: "u";→.u-button、.u-card__header - 搭配
@mixin b($name)封装块定义,避免手写拼接出错 - 禁止在
button.scss里@import "../card/card.scss"—— 路径依赖会让 button 强绑定 card 版本,升级 card 就得测所有引用它的地方
真正需要组合的场景(如 card 里用 button),只在 HTML 层面对齐类名:<div class="card"><button class="button button--primary">,样式文件之间保持零 import。
<p>最常被忽略的一点:目录结构不是为了“看着整齐”,而是让 <code>git blame 和 PR review 能快速定位影响范围——改 /components/button/button.scss,你就知道只会影响所有 .button 实例,不会误动 .modal__close。











