合法的父子搭配需要显式约束
别名: 复合约束 · 合法子节点 · compound constraints · allowed children
概念解释
树默认允许任意节点互相嵌套。设计系统里的父子却不是任意的:选项只能放进选择器,列头只能放进表格,菜单项只能放进菜单。父子搭配约束(parent-child composition constraints)把「谁可以做谁的孩子」写成可被检查的规则,而不是写在说明文字里等人记。合法搭配被显式化之后,非法嵌套在编辑和构建时就被拒绝,而不是渲染出一块看起来像、行为却对不上的控件。
约束的对象是角色,不是类名字符串。<Select> 接受 <Select.Option>,不接受随便一个 <div> 冒充选项——即使那个 div 画得一模一样。画得像不能代替「我是选项」这条契约。
机制
开放组合的默认值来自通用 UI 树:浏览器和框架都允许几乎任何元素互套。设计系统的组件却共享内部协议:选中态往上冒泡、键盘方向键在兄弟间移动、当前项 id 写回父级。没有协议的节点走进这棵树,父级读不到约定字段,子级也收不到「你在列表第几项」的信号。画面可能还能拼出来,状态和键盘已经断了。
口头约定拦不住复制粘贴。显式约束把协议提升为类型、运行时断言或 HTML 内容模型:父级的子类型白名单、子级对父级上下文的必需声明。检查发生在调用点闭合的那一刻——正是组合空间真正被使用的地方。说明文字发生在调用点之外,读到的人与写错的人往往不是同一个。
边界
真正通用的布局容器(栈、网格)必须接受任意子节点,对它们加白名单等于禁止排版。跨系统嵌入的第三方小部件无法参与内部协议,约束应退到外壳,而不是假装它是合法子项。约束过窄会把合理的扩展(在选项里嵌一套自定义内容)误判为非法;过宽又回到「看起来像就行」。外观档位之间的选择,不是父子合法性问题。
怎么落地
- 为每个复合体写一张允许表:父角色、合法子角色、是否允许多个、顺序是否有意义。表是接口的一部分,跟属性一同发布。
- 用类型或静态检查落实允许表;做不到静态时,在开发构建里对非法子节点抛错,并指出应收的父级。
- 子组件在合法父级之外单独使用时必须失败,而不是静默画成「没选中、没键盘」的空壳。
- 验证:写两份调用,一份把选项塞进菜单,一份把菜单项塞进选择器。两份都应在编辑器或构建期失败,且报错点名正确父级。再写一份合法嵌套,键盘方向键与选中态必须走通。若只有说明里写了「不要这样用」,而构建能通过,约束就还不存在。