每个树节点必须是独立 block,即 tree-node 块,自包含展开/折叠、图标状态、缩进逻辑;缩进用 data-level 属性选择器实现,禁止用 tree-node--level-2 等表现类名;图标命名为 tree-node__icon 并用修饰符控制状态;复杂内容应抽为独立 block 如 person-card。

每个树节点必须是独立 block,不能塞进一个 tree 块里无限嵌套
真实项目中最常踩的坑,就是写了个 tree__node,结果发现它依赖父级 tree__children 才能缩进或展开——这已经违背 BEM 的“块独立”原则。树形结构天然递归,但 BEM 要求每个可复用单元自包含。
正确做法是:每个节点自身就是一个 tree-node 块。它的展开/折叠、图标状态、缩进逻辑全部封装在内部,不向外暴露结构依赖。
-
tree-node是块,可单独渲染、测试、复用 -
tree-node__children只是布局容器,不带业务逻辑,也不控制子节点样式 - 子节点是另一个
tree-node实例,不是tree-node__node或tree-node__children__node
缩进必须用 data-level + 属性选择器,别写 tree-node--level-2
缩进是视觉表现,不是语义层级。BEM 明确反对用类名表达“第几级”,因为树深度一变,HTML 就得批量改,CSS 也得跟着调。
推荐方式是:JS 渲染时给每个 tree-node 添加 data-level="2" 属性,CSS 用属性选择器控制:
立即学习“前端免费学习笔记(深入)”;
.tree-node[data-level="0"] { padding-left: 0; }
.tree-node[data-level="1"] { padding-left: 24px; }
.tree-node[data-level="2"] { padding-left: 48px; }
- 避免后代选择器:
.tree-node__children .tree-node会随 DOM 深度增加权重,且易被结构变更破坏 - 不要用
tree-node--level-2这类纯表现类名,它让 HTML 和 CSS 强耦合 - 如果用 DOM 层级推导缩进,可用
:scope > .tree-node__children > .tree-node,但不如 data-level 稳定
图标和展开按钮必须作为 tree-node__icon 元素,修饰符只能挂载在元素上
图标不是独立组件,而是 tree-node 的一部分,所以命名必须落在块内:tree-node__icon,而不是 icon 或 tree-icon。它的状态(展开/收起/禁用)通过修饰符体现,比如 tree-node__icon--expanded。
- ✅
tree-node__icon--expanded:图标自身的状态 - ❌
tree-node--expanded__icon:语法错误,BEM 不支持修饰符挂载在元素上 - ⚠️
tree-node--expanded和tree-node__icon组合使用才是正交方案 - 禁止把图标状态写成
tree-node--icon-expanded,这混淆了块与元素边界
复杂内容要升格为独立 block,别硬套 __ 嵌套
当树节点里嵌了带操作栏的部门卡片、含头像和联系人的人员节点,别继续往 tree-node__content__header__title 里钻。多层嵌套会破坏可读性、复用性和测试隔离性。
正确做法是:把复杂内容抽成独立 block,比如 person-card 或 department-summary,然后在 tree-node__content 中直接引入。
- 每个新 block 都有自己的样式作用域,不会污染
tree-node - JS 查询和事件绑定更清晰:
document.querySelector('.person-card')比document.querySelector('.tree-node__content__header__title')更可靠 - 后续要复用人员卡片到列表页或弹窗,不用重写样式或调整层级
最易被忽略的一点:BEM 的块边界不是由 DOM 嵌套决定的,而是由功能职责决定的。哪怕两个 tree-node 在 DOM 中隔着三层,只要它们承担相同职责,就该共享同一套 class 命名和样式契约。


















