avatar

2026-04-09 | 组件库

原子化组件库的 Space,为什么最终去掉了那层 DOM

Helen

从老版 TDesign 的 Space 设计出发,梳理 atomic 版本里移除 `space-item` 包裹层的原因,以及在 separator 与 Svelte children 处理上踩过的坑。


其实原子化组件库最初并不打算提供 layout 类组件,直接使用 gap 等原子布局能力即可,额外增加一层 DOM 反而会带来负担;但考虑到后续 A2UI 对语义化表达的需求,仍有必要补充一层轻量的语义化封装。

一开始的疑问:为什么要多一层 DOM?

老版 TDesign 的 Space,每个子元素外面都会包一层:

<div class="t-space-item">
  {child}
</div>

第一反应其实是,这一层是不是多余的。 后面对着功能和源码看了一圈,基本能确认它的作用:

  • margin 模拟 gap(历史兼容)
  • 控制子项宽度 / stretch
  • 插入 separator
  • 做一层样式隔离

也就是说,这层不是随便加的,是为了兜住早期浏览器能力不足的问题。


但问题来了:现在还需要吗?

放到现在这个 atomic + 现代浏览器 的前提下再看一遍:

  • CSS gap 已经全支持了
  • flex / grid 本身就能控制布局
  • separator 可以直接插节点
  • 样式隔离在容器层已经足够

再对照一遍,会发现这些问题都已经不成立了。 所以 space-item 的角色就变成: 只是维持一个不再需要的中间层。

直接结论: 多余 DOM,可以去掉。


删完之后遇到的第一个问题:separator 不对

把 wrapper 干掉之后,第一个出问题的是 separator 里的 Divider: 它不撑开了。

一开始的处理方式是给它加一条 CSS:

align-self: stretch;

功能上没问题,但不太确定是不是最合理的方式。 回头看了 TDesign Next 的实现,它的策略更简单:

  • 水平方向不处理(依赖默认 stretch
  • 垂直方向加一条 width: 100%

本质是:尽量不干预 flex 默认行为,只在必要的时候补一条规则。 最后改成:

direction === 'vertical' ? { width: '100%' } : {}

这个写法更明确,也更接近原实现的意图。


又来个坑:Svelte 这边没法“插 children”

React / Vue 这类框架可以直接处理 children

  • React:Children.toArray()
  • Vue:slots.default?.()

可以拿到数组,然后在相邻元素之间插 separator。 但 Svelte 5 不一样。

children 是一个 Snippet,本质是渲染函数,只能整体执行:

{@render children()}

不能拆开,也不能遍历。


这个限制是怎么确认的

对 Svelte 不太熟悉,一开始其实不太确定是不是我用法不对。 直觉上应该是可以像 React 一样,把 children 拆成数组处理的,所以去查了一下。

很快搜到一个官方 issue(sveltejs/svelte#11566),讨论的就是:

能不能像 React 一样遍历 children

官方结论比较明确:

  • Snippet 是编译时概念,不是运行时数组
  • 不支持把 children 当作可迭代结构使用
  • 也不会自动聚合为数组

看到这里基本就能确认: 不是写法问题,是框架本身就不支持这条路径。 中间也尝试过在外面包一层再处理,但本质还是拿不到子节点结构,最后就放弃了这条思路。


换一条路

既然不能操作 children,那就只能从 DOM 和布局层绕。 最后用了一个比较直接的方案:

方案

  1. 正常渲染所有 children
  2. 同时预渲染一组 separator
  3. 在运行时统计真实子节点数量
querySelectorAll(':scope > :not([data-slot="space-separator"])')
  1. 用 CSS order 排序
  • child:偶数
  • separator:奇数

最终视觉效果是:

child separator child separator child

但 DOM 实际是平铺的。


最后的结果

这一轮调整之后:

  • 去掉了 space-item 这一层 DOM
  • 间距完全交给 gap
  • separator 作为兄弟节点参与布局
  • 多框架实现可以对齐

小结

这次改完之后,反而更容易理解原来的实现。 space-item 本身没有问题,它只是针对当时的约束做出的设计。 现在约束变了,实现自然也应该跟着变。

© 2026 Hailin. All rights reserved.