R1.03.6externally driven observable state设计

状态需在组件外可被驱动与观察

别名: 可控状态 · data-state · 可测试状态 · Storybook controls

概念解释

只活在 :hover 或内部计时器里的状态,父级、测试和文档都握不住。状态必须能从组件外驱动(用 prop、data 属性或参数打开),也必须能从外面观察(用 data-state、ARIA 或稳定类名读到)。做不到这两点,状态表只对作者的鼠标成立,对评审和回归都不成立。

驱动让 Storybook 和自动化不必模拟真的悬停或真的慢网;观察让断言不必去比截图像素。两者缺一,状态就还是副作用。

机制

CSS 伪类把状态绑在指针位置上,测试运行器要么发不可靠的悬停事件,要么根本到不了焦点可见那种「键盘才出现」的格。内部 setTimeout 把加载绑在真实时钟上,文档里的加载帧会在截图前自己结束。外部驱动把这些格变成输入:data-state="loading"isDisabled 让任何调用方——包括测试——直接置位。

观察是对偶。没有稳定的对外钩子,视觉回归只能整页截图,读屏测试只能模糊匹配文本。aria-busyaria-invaliddata-state 让查询写得像合同。父级也需要观察:表格要根据行是否在提交来禁用整表操作。状态若不肯出组件边界,组合层只好去探内部 DOM,一改类名就全碎。

边界

纯装饰、从无脚本接入的静态站点,没有外部驱动方,观察也可退化成「看得到」。浏览器原生控件的部分状态(系统级 :disabled)已经可被选择器和辅助技术观察,不必再包一层自定义属性,但自定义控件没有这条免费通道。无法在服务端渲染的瞬时悬停可以保持伪类,仍应提供等价的强制 prop 给文档用。安全敏感的内部标志(风控锁定)可以对普通父级隐藏,但仍需对测试账号或专用查询通道可观察,否则那一格永远无法回归。

怎么落地

  • 为每个公开状态提供输入(prop 或 data 属性)和输出(ARIA 或 data-state),并在组件文档里写明两者的名字。
  • Storybook 用控件面板点亮每一格,禁止「把鼠标移上去看悬停」作为唯一演示手段。
  • 自动化测试通过置位输入打开禁用、加载、错误,再断言对应的输出钩子,不靠像素比对当唯一依据。
  • 验证:在不移动指针、不改系统时钟的情况下,外层测试能否单独打开并读回每一格。做不到的那一格,状态仍锁在组件内部。

延伸

  • 同组R1.03.1 每个组件都需定义全部交互状态 · R1.03.2 加载、空、错误状态最常缺失 · R1.03.3 状态缺失会在实现阶段被随意补齐 · R1.03.4 状态之间的转换比状态本身更易出错 · R1.03.5 状态可叠加,组合态需要优先级规则
  • 相邻R3.09 语义化结构与可访问性实现 · R3.01 语义结构
  • 站内检索externally driven observable state · data-state · aria-busy

同组卡片

快捷操作

分享

分享当前页面

ios_share

https://hci.top/zh/handbook/R1.03.6