状态需在组件外可被驱动与观察
别名: 可控状态 · data-state · 可测试状态 · Storybook controls
概念解释
只活在 :hover 或内部计时器里的状态,父级、测试和文档都握不住。状态必须能从组件外驱动(用 prop、data 属性或参数打开),也必须能从外面观察(用 data-state、ARIA 或稳定类名读到)。做不到这两点,状态表只对作者的鼠标成立,对评审和回归都不成立。
驱动让 Storybook 和自动化不必模拟真的悬停或真的慢网;观察让断言不必去比截图像素。两者缺一,状态就还是副作用。
机制
CSS 伪类把状态绑在指针位置上,测试运行器要么发不可靠的悬停事件,要么根本到不了焦点可见那种「键盘才出现」的格。内部 setTimeout 把加载绑在真实时钟上,文档里的加载帧会在截图前自己结束。外部驱动把这些格变成输入:data-state="loading" 或 isDisabled 让任何调用方——包括测试——直接置位。
观察是对偶。没有稳定的对外钩子,视觉回归只能整页截图,读屏测试只能模糊匹配文本。aria-busy、aria-invalid、data-state 让查询写得像合同。父级也需要观察:表格要根据行是否在提交来禁用整表操作。状态若不肯出组件边界,组合层只好去探内部 DOM,一改类名就全碎。
边界
纯装饰、从无脚本接入的静态站点,没有外部驱动方,观察也可退化成「看得到」。浏览器原生控件的部分状态(系统级 :disabled)已经可被选择器和辅助技术观察,不必再包一层自定义属性,但自定义控件没有这条免费通道。无法在服务端渲染的瞬时悬停可以保持伪类,仍应提供等价的强制 prop 给文档用。安全敏感的内部标志(风控锁定)可以对普通父级隐藏,但仍需对测试账号或专用查询通道可观察,否则那一格永远无法回归。
怎么落地
- 为每个公开状态提供输入(prop 或 data 属性)和输出(ARIA 或
data-state),并在组件文档里写明两者的名字。 - Storybook 用控件面板点亮每一格,禁止「把鼠标移上去看悬停」作为唯一演示手段。
- 自动化测试通过置位输入打开禁用、加载、错误,再断言对应的输出钩子,不靠像素比对当唯一依据。
- 验证:在不移动指针、不改系统时钟的情况下,外层测试能否单独打开并读回每一格。做不到的那一格,状态仍锁在组件内部。