State
State 规范
State 是什么?
Section titled “State 是什么?”State 是 Proto UI 提供的 host-neutral 状态表达方式。它用于定义状态、存储状态,并记录状态随时间发生的变化。
State 不是信息通路。它不负责把 App Maker 的配置送入组件,不负责把组件结果反馈给用户,也不负责组件之间共享环境。它表达的是 Component 自己在时间中的内部连续性。
State 也不是副作用机制。修改 state 不会隐式触发 render、commit、feedback flush 或任何宿主副作用。状态变化会被记录下来;要让 UI 反映变化,需要显式 update、rule、feedback 或其他契约定义的路径。
Definition 与 State Machine
Section titled “Definition 与 State Machine”State 的原型作者侧入口是 def.state.*。从 API 形状上看,它唯一的核心动作是“定义一个状态”,而这个动作是 setup-only。
const open = def.state.bool('open', false);const placement = def.state.enum('placement', 'bottom', { options: ['top', 'right', 'bottom', 'left'] as const,});def.state.* 不会把一次运行时数据写进组件。它定义一个 state slot,并返回这个 slot 的受控 view。这个 view 背后是一个状态机:它有默认值、当前值、变更记录、watcher 与生命周期边界。
State definition 完成之后,state view 会跨 setup 与 runtime 存在。view 上的不同 API 有不同 phase 规则:
| API | Phase | 语义 |
|---|---|---|
get() | setup + runtime | 读取当前值;setup 期间读取当前默认值 |
setDefault(value) | setup-only | 设置默认值 |
set(value, reason?) | runtime callback-only | 在运行期改变当前值 |
watch(callback) | setup-only | 注册对 borrowed/observed state 的观察 |
get() 是特殊的:它在 setup 与 runtime 都可用。setup 期间还没有真实 runtime mutation,因此读到的是当前默认值;进入 runtime 后,它读到状态机的当前值。
State View
Section titled “State View”原型作者不会拿到裸 state slot 或裸数据源。App Maker 即使通过 expose 得到 state,也不会得到这个内部数据源。所有访问都通过 view 完成。
原型作者侧有三种 view:
| View | 适用场景 | 能力 |
|---|---|---|
owned | 当前原型创建并完全控制该 state | get、setDefault、set |
borrowed | 当前原型可控制但不完全拥有该 state | get、setDefault、set、watch |
observed | 当前原型完全不能控制该 state | get、watch |
Owned View
Section titled “Owned View”Owned view 表示这个 state 由当前原型直接创建,并且完全受当前原型控制。
const open = def.state.bool('open', false);
def.event.on('press.commit', () => { open.set(!open.get());});Owned view 故意不提供 watch()。这是一个主动设限:原型作者不应该把自己完全控制的状态变化写成监听副作用。若需要让 state 变化影响输出,应显式调用 update、使用 rule,或通过更明确的协作机制表达。
Borrowed View
Section titled “Borrowed View”Borrowed view 表示当前原型可以控制这个 state,但它不是完全且直接的所有者。常见例子是 asHook state 与官方 interaction state。
const pressed = def.state.fromInteraction('pressed');
pressed.watch((run, event) => { if (event.type === 'next') { run.update(); }});Borrowed view 既能 set(),也能 watch()。它适合“可控制但不完全拥有”的状态:当前原型可以影响它,但不应该把它解释成完全私有状态。
Observed View
Section titled “Observed View”Observed view 表示当前原型完全不能控制这个 state,只能读取和观察它。它不提供 setDefault() 或 set()。
Observed view 适合事实来源在别处的状态。当前原型可以响应它,但不能改写它。
Value Spec
Section titled “Value Spec”State definition 只承认有限的 host-neutral state kind:
| API | Kind | 要求 |
|---|---|---|
def.state.bool | bool | boolean value |
def.state.enum | enum | 必须声明 options |
def.state.string | string | 可用 options 等 metadata 描述值域 |
def.state.numberRange | number.range | 必须声明 min 与 max |
def.state.numberDiscrete | number.discrete | 可用 min、max、step 等 metadata 描述值域 |
这些 state value 仍然需要满足 JSON-compatible value 边界。State 不要求 function、symbol、class instance、DOM object 或其他 host object 才能表达语义。
metadata 也必须保持 host-neutral。比如 enum options、number range、step 都是 portable value-domain 描述;它们不应该绑定到某个宿主控件或宿主对象。
当前 value-domain validation 仍是治理断口:契约已经要求定义值域,但运行期是否对所有 definition、setDefault 与 set 执行完整校验,还没有被当作已完成保证。
Watch 与 StateEvent
Section titled “Watch 与 StateEvent”watch() 是 setup-only registration API,只存在于 borrowed 与 observed view。它用于观察状态变化,不是响应式渲染系统。
watch callback 接收 StateEvent:
state.watch((run, event) => { if (event.type === 'next') { // event.prev // event.next // event.reason }});当 state value 真的从 prev 变成 next 时,watcher 会收到:
{ type: 'next', prev, next, reason? }如果 Object.is(prev, next) 为 true,这次写入不构成实际 state change。setDefault() 也不派发 next event,因为它只改变默认值计划,不代表 runtime value transition。
实例 unmount/dispose 造成 state slot 与 watcher 断开时,watcher 可以收到:
{ type: 'disconnect', reason: 'unmount' }watch callback 本身不得隐式触发 render、commit 或 update。它只是记录和观察状态变化;是否更新 UI 仍然需要显式路径。
Interaction State
Section titled “Interaction State”Interaction state 是 State 的一个子领域,也可以理解成 State + Event 的复合模块。
const hovered = def.state.fromInteraction('hovered');const disabled = def.state.fromInteraction('disabled');声明 interaction state 会隐式引入 runtime-managed event wiring。原型作者不需要手写 pointer.enter、pointer.leave、host:focus 等 event subscription 来维护这些官方交互状态;runtime package、module、adapter 与 host capability 会协同完成这件事。
同一个交互主体上的同类 interaction state 永远是同一份 state reference。某个 asHook 内部如果调用 fromInteraction('pressed'),调用这个 asHook 的原型作者之后也可以直接调用 fromInteraction('pressed') 拿到同一份状态,而不必由 asHook 显式返回。
当前 interaction state 采用 borrowed view。长期上,某些 interaction state 可能更接近 observed view;但在 adapter 与 module 还不能绝对保证真实交互状态的阶段,允许原型作者修正这类状态仍然是必要的逃生能力。
Lifecycle
Section titled “Lifecycle”State view 绑定到组件实例生命周期。
实例存活期间,view API 按 phase rule 可用。unmounted callback 执行期间,实例仍被视为存活;符合 runtime phase 的 state 操作仍然允许。
实例 dispose 后,所有 state view API 与 watch registration 都必须失效。内部 subscription 也必须清理,避免 dispose 之后仍然调用旧 watcher。
契约实体预览
Section titled “契约实体预览”与测试的关系
Section titled “与测试的关系”State 的测试映射分为本域和 interaction 子领域:
| 测试实体 | 主要覆盖 |
|---|---|
T-STATE-0001 | definition、owned view phase guard、no-implicit-render |
T-STATE-0002 | owned、borrowed、observed、exposed state view capability |
T-STATE-0003 | finite kind、value spec metadata、JSON-compatible boundary |
T-STATE-0004 | watch setup-only、StateEvent、re-entrant order、lifecycle cleanup |
T-STATE-INTERACTION-0001 | interaction event wiring、callback order、shared subject identity |
这些测试把 state 从“内部变量”收敛为一组可执行的状态机边界:什么时候能定义、什么时候能读写、谁有能力写、谁只能观察,以及变化记录如何派发。
与其他规范的关系
Section titled “与其他规范的关系”Core定义 setup/runtime、JSON value boundary 与 channel model;State 明确不属于信息通路。Props表达 App Maker 到 Component 的配置输入;State 表达 Component 自身的内部连续性。Event表达 User 到 Component 的交互输入;interaction state 会使用 runtime-managed Event wiring 来维护官方交互状态。Feedback把组件状态和结果传达给 User;State 自身不负责可感知呈现。Rule可以读取 state 并执行 feedback intent,但 rule 是独立 API,不是 State 的一部分。Lifecycle定义 state view 在 setup、runtime callback、unmounted 与 dispose 之间的可用边界。