范围:本导览只管
packages/xiaoye-ui/src里「布局与容器」这一族(Grid/Row/Col、Flex、Layout、Space、Divider、Splitter、Card、Panel、Collapse、BlockUI、ScrollPanel、Toolbar、Fieldset)的选型与踩坑路由,共 14 篇(编号C02-00到C02-14,中间有一个空号)。 不管:主题 Token 的四层派生(A03)、cssinjs 的注册与 hashId(A04)、注入链的默认值合取(A05)、浮层定位与层级(A07、I01、I10)、动效内核(I09)。这一类牵到那几条的,下面只给编号。
这一类的第一件事是三代布局并存且互不调用。C02-00 的六个论点把这件事拆得很硬:间距的代际差异不在「用不用 gap」,而在哪一半的间距允许用 gap(论点 1);栅格的断点被实现了两遍、两遍语义不对齐(论点 2);三代接 Design Token 的深度是三档,其中「看起来接了」的那一档其实没接(论点 3);布局信息的传递按代际分成值传递、结构传递、context 广播三种(论点 4);三代都靠「JS 拼类名 + CSS 枚举类名」的双表结构,但只有 flex 的两张表同源(论点 5);SSR 与首帧一致性也只有 flex 天然成立,栅格额外带一个「首屏取最宽档」的初值(论点 6)。结论:从 Grid 迁到 Flex 或反过来,不是换个标签,而是换一套判定语义。
第二件事是容器家族只有一份真 DOM,其余是移植副本。C02-07 论点 6 写明 Card / Grid / Meta 的「继承」只是共享前缀加单棵样式树,而 _shared/CardLike 是被 Panel 单走的平行副本;C02-08 论点 1 从另一侧证实 Panel 的 render 里没有一行 DOM,它只是「状态持有 + 槽位翻译」层,而被翻译进去的 CardLike 只有它一个客户。C02-09 论点 1 给出第三种形态:Collapse 的面板不是自治组件,而是每轮渲染被 cloneElement 重写的模板,面板身份取 vnode key、缺 key 时退化为渲染顺序下标。看到「带标题栏、可折叠」的组件不要 assumed 它们共享实现——本类有三种不同的组装方式。
第三件事是这一类最高频的公开面缺陷:inheritAttrs: false 却不读 attrs。按严重程度排一条链:C02-13 论点 2(Toolbar 连 class 与 style 都丢,被登记为「API 面存活、实现死亡」族里最严的一例)→ C02-14 论点 1(Fieldset 外部属性在根节点被无声吞掉)→ C02-08 论点 6(Panel 把 class 当对象键用,$attrs 被两层 inheritAttrs: false 全部丢掉)→ C02-06 坑 2(写在 <xy-splitter> 标签上的 style/class 静默丢失)→ C02-02 论点 1(Flex 与外界只有 class 与 style 两条通路,其余 attrs 没有任何落点)。同族还有 prefixCls 这条契约:C02-11 论点 3 里 prefixCls/direction 被 useConfigInject 读取却没在 props 里声明,于是逐实例覆盖是死的;C02-06 坑 3 是只给根设前缀导致面板与分隔条集体失配。
第四件事是死声明密度极高,且形态互不相同:类名生成了没有规则(C02-08 论点 4 的 -collapsed、C02-04 论点 2 的 -align 与 -horizontal);规则写了但条件永不成立(C02-03 论点 6 的 min-width: 0、C02-10 论点 5 的全屏阻塞锁上类没锁住滚动);属性声明了但被后面的无条件赋值覆盖(C02-02 论点 3 的 stretch);两条动画路径都不成立(C02-14 论点 2 的 grid-template-rows 与 max-height);样式引用了全仓从未声明的 CSS 变量(C02-14 论点 3 的 --xy-border-radius,而正确 Token 就在同一个解构里);一个目录里两个 genComponentStyleHook 对同一批选择器各写一遍(C02-06 论点 7)。排查时先分诊是哪一种,再决定是补 CSS 还是删声明。
第五件事是首帧与 SSR。栅格有两个初值相反、更新条件不同的 screens 状态机,「首帧 gutter 偏大」与「首帧响应式 align/justify 全丢」是同一处设计缺陷的两面(C02-01 论点 1);supportFlexGap 的初值把首帧永久钉在 padding 补偿方案上,翻转发生在 onMounted(C02-01 论点 5);Layout 的响应式折叠不测量 DOM、用 max-width 媒体查询且注册在 onMounted 内,因此首帧与服务端一定「不折叠」(C02-03 论点 4);ScrollPanel 的内容 id 用 Math.random() 生成,aria-controls 关联在水合后断裂(C02-11 论点 4);Fieldset 的 id 用模块级自增计数器现场生成,绕开了仓库已有的 SSR 安全 useId(C02-14 论点 6)。
| 要解决的问题 | 选它 | 而不是另一个 | 判据 |
|---|---|---|---|
| 多栏等分、要断点响应式 | Grid 的 Row 与 Col(C02-01) | Flex 加手写媒体查询 | 断点体系在 JS 判档与 CSS 媒体查询里各一份,只覆盖 screenSM 会让两侧分家(C02-01 坑 6、C02-00 论点 2) |
| 一维排列且要 SSR 与首帧一致 | Flex(C02-02) | Grid | 三代里只有 flex 天然成立,栅格带「首屏取最宽档」的初值(C02-00 论点 6) |
| 只要「子节点之间」有间距,还要分隔符与紧凑模式 | Space(C02-04) | Row 的 gutter | gutter 的「间距的一半」这门算术在 Row 与 Col 两侧各写一遍、靠除数互为相反数偶然对齐(C02-01 论点 4) |
| 整页骨架:头、侧栏、内容、脚 | Layout 家族(C02-03) | 四个 div 自拼 | Header/Footer/Content 是一个高阶函数造的适配器,样式由 Layout 根节点代持,离开根就没有样式(C02-03 论点 1) |
| 面板尺寸可拖拽、可键盘调整 | Splitter(C02-06) | Collapse | 折叠面板没有尺寸协商;Splitter 在 flex-basis 上交汇百分比与像素,减项按面板总数线性放大(C02-06 论点 2) |
| 一个带标题栏与操作区的静态容器 | Card(C02-07) | Panel | Panel 的 render 里没有一行 DOM,它只是把槽位翻译进 CardLike(C02-08 论点 1) |
| 需要「折叠但保留内容在 DOM 里」 | Panel(C02-08) | Collapse | Panel 同一个文件里两套相反策略:正文隐藏不卸载、底部直接卸载(C02-08 论点 4) |
| 多面板互斥展开、要 activeKey 语义 | Collapse(C02-09) | 若干 Panel 手拼 | 面板身份来自 vnode key,Collapse 每轮渲染用 cloneElement 重写面板(C02-09 论点 1) |
| 给一块内容加遮罩使其不可交互 | BlockUI(C02-10) | Spin(属 C11) | blocked 是一次性命令而非受控 prop,真状态住在 isBlocked,两个写入口之间没有对账路径(C02-10 论点 1) |
| 要自定义滚动条外观与抓取手感 | ScrollPanel(C02-11) | 原生 overflow 加 CSS | 两条自绘滚动条靠几何反算同步原生 scroll,含 10% 拇指下限与负偏移定位(C02-11 论点 7) |
| 左中右三区的工具条 | Toolbar(C02-13) | Space | 它的间距是 CSS 单轨直出,既不 import Space 也不做 flex-gap 能力探测(C02-13 论点 1) |
align/justify 首帧完全不生效 → C02-01 坑 1、坑 2;根因是 Row 内并存两个初值相反、更新条件不同的 screens 状态机(论点 1)。span 超过 24 静默无效,且分栏数改不动 → C02-01 坑 3。gutter 类型接受 xxxl,运行时永远取不到 → C02-01 坑 4:这是一个「类型允许、运行时无效、CSS 侧根本没有」的半接入档位,四组独立证据在三个文件里互相印证(论点 3)。C02-01 坑 5(论点 2:useResponsiveObserve() 返回每实例一份的 computed)。offset/order/push/pull 传 0 时,非响应式与响应式两条路判定相反 → C02-01 坑 7。import { Grid } from 'xiaoye-ui' 拿到的不是组件;xiaoye-ui/row/style 与 xiaoye-ui/col/style 是空模块 → C02-01 坑 9、坑 10(论点 6 的导出面三分裂),生成物口径见 A02。Flex 上写了属性没生效:先分诊三条断的通道——stretch 是可达 CSS、不可达属性值(utils.ts:48 的无条件赋值把它盖掉,C02-02 论点 3);prefixCls 是唯一会被 spread 到 DOM 的公开 prop,因为 omit 名单漏了它(论点 2);gap 的三条字面量各自为政、本该管住数字分支的函数零调用点(论点 4)。ConfigProvider 的 vertical/flex 键没有效果 → C02-02 论点 6:那条 context 回落分支是死代码,ConfigProvider 从来不提供 flex 这个键。Space 的 split 写成 prop 后变成一个 DOM 属性 → C02-04 论点 3:分隔符只从插槽进,文档 API 表里那一栏没有对应的 prop 声明。size 时漏出 undefinedpx → C02-04 论点 4:size 从入参到像素要走 computed、watch、两个 ref 三跳,而全局配置的通道只允许标量。C02-04 论点 5:跨组件契约压在一个字符串分隔符上,判定是 AND 语义。minSize 钳制 → C02-06 坑 1(论点 1:那对 computed 首次求值后永久冻结)。NaN 写进 flex-basis 与存储 → C02-06 坑 4(论点 5:document 监听器与 gutter 内联属性双轨并行,其中一次坐标是 undefined)。state-key 之后改 size 不再生效 → C02-06 坑 5;窗口缩放后面板比例漂移且永不纠正 → 坑 7(论点 6:只有 onMounted 与 onUpdated 两个时机)。resizestart 与 resize 同频外发 → C02-06 坑 8:键盘 resize 用 40ms setInterval 重放整个 onResizeStart,每秒 25 次(论点 4)。C02-08 论点 3:Panel 无条件提供槽位函数,把「有没有头部」的判据写死了。activeKey 改成 undefined 会收起全部面板 → C02-09 论点 2:初始化只在 setup 执行一次,回流 watcher 却无守卫。collapsible="icon" 的图标点击无效 → C02-09 论点 4:分区触发被拆在两个组件里,父侧读的是自身 props。leave 钩子 → C02-09 论点 3:开关不在 motion 里,而在 CollapsePanel 的三元表达式里。C02-10 论点 5;同一份遮罩 CSS 在区块模式与全屏模式下命中率不同 → 论点 3(样式挂在 componentCls 的后代上,遮罩被搬出了这个后代关系)。ScrollPanel 的 aria-controls 在水合后断裂 → C02-11 论点 4;data-* 状态通道没人读且自相矛盾 → 论点 2;expose 把 25 个成员钉成公开契约而测试只用其中一个 → 论点 6。Toolbar 不响应 RTL,且 class/style 全丢 → C02-13 论点 4、论点 2;容器上的 justify-content: space-between 是惰性声明,真实驱动是 center 的 flex: 1 → 论点 3。Fieldset 折叠只有淡入淡出、圆角不生效 → C02-14 论点 2、论点 3;{...props.toggleButtonProps} 最后展开、按字面顺序覆盖内部处理器与 ARIA 状态 → 论点 5。Divider 的 type 与 orientation 名字与标准语义正好相反 → C02-05 论点 3;垂直分割线的几何用 em 字面量硬编码、不参与 token 体系 → 论点 7。gutter 或响应式档位:C02-00 论点 2 → C02-01 论点 1、论点 3 → C02-01 坑 6(JS 判档点与 CSS 媒体查询分家)。C02-11 论点 3(props 未声明)、C02-06 坑 3(只设根前缀)、C02-08 论点 2(类名前缀三个来源,其中一个是字面量),机制侧归 A04。C02-05 论点 2(靠复合选择器的类个数阶梯,不靠 !important 与样式顺序)→ C02-07 论点 5(bordered 与 hoverable 是一张类名开关表驱动两条互斥 Token 分支)。C02-07 论点 6 与 C02-08 论点 1 确认副本关系,再看 C02-09 论点 1 决定是自治组件还是 cloneElement 模板;不要新建第四份 CardLike。C02-09 论点 3、论点 5(折叠 CSS 嵌在 componentCls 之下,必须逐组件引入,本仓引入点是 5 处而不是 3 处)→ I09 → C02-10 论点 2(卸载被拆成三份手工记账)。C02-13 论点 2 → C02-14 论点 1 → C02-08 论点 6 → C02-06 坑 2 → C02-02 论点 1 这条链逐个核 inheritAttrs 与 attrs 落点。C02-01 论点 5 → C02-03 论点 4 → C02-11 论点 4 → C02-14 论点 6,机制归 A04 的注水段。C02-10 论点 6(zIndex/autoZIndex 那一面)→ I10。Compact):C02-04 论点 5、论点 6(同一个样式钩子被两个组件以不同前缀调用,规则作用域是「谁调用谁得到」)。