asHook
asHook 规范
asHook 是什么?
Section titled “asHook 是什么?”asHook 是 Proto UI 中一种特殊的原型形态。它像原型一样拥有 setup,可以声明 props、state、event、feedback、expose、rule、lifecycle 等能力;但它失去了独立交互主体,只有附着到另一个 prototype 的 setup 期间才成立。
直观地说,asHook 表达的是“成为某种交互身份”或“继承某套行为协议”。例如一个原型调用 asFocusable() 后,它不是组合了一个新的子组件,而是让当前原型获得可聚焦的交互身份与相关能力。
asHook 不是信息通路,也不是 prototype-level composition。它不创建独立组件实例,不创建独立 runtime instance,不拥有自己的 template root。它引入的效果都归属调用者 prototype。
asHook 与 use-style hook
Section titled “asHook 与 use-style hook”asHook 的语义更接近继承:调用者声明自己是某类交互主体,或者继承某组行为协议。它通常不带参数,且默认只生效一次。
组合式逻辑复用更接近 useXxx 风格 hook。若一个 helper 只是普通函数封装,原型作者可以自行编写函数;只有当它需要 trace、capture、setup guard、artifacts、disposers 或冲突诊断时,才值得成为受治理 API。
defineHook / defineUseHook 的命名与 API 边界仍是断口,不属于当前 asHook 核心契约。
defineAsHook(spec) 至少接受与 definePrototype(spec) 兼容的定义描述:
import { defineAsHook } from '@proto.ui/core';
export const asPressed = defineAsHook({ name: 'as-pressed', setup(def) { const pressed = def.state.fromInteraction('pressed');
def.expose.state('pressed', pressed); },});这里的 name 是 prototype spec name,遵守原型的 kebab-case 命名规则。作者代码中导出的 caller binding 应呈现为 asXxx,但运行时无法可靠知道返回值被赋给了哪个变量;因此 asXxx caller binding 的检查属于 lint、CLI、生成器或 TypeScript tooling 的后续工作。
asHook 的 setup 返回值仍遵守 prototype setup 契约:只能返回 render function 或 void。AsHookResult 不是 setup 的直接返回约定,而是 asHook runtime 根据捕获到的 setup effects、handles、artifacts 与可选 render return 生成的 caller 返回值。asHook 返回的 render fragment 不会自动提交,调用者必须显式组合它。
asHook caller 只能在调用者 setup 期间调用:
export const button = definePrototype({ name: 'button', setup(def) { asTrigger(); const focusable = asFocusable(); focusable.configure({ disabled: false }); const pressed = def.state.fromInteraction('pressed');
return () => ({ type: 'button', style: pressed.get() ? tw('translate-y-px') : undefined, children: 'Button', }); },});render、lifecycle callback、event callback、props watcher、context watcher 或其他 runtime 期间都不得新增 asHook 应用。runtime 中需要的行为必须由 asHook 在 setup 期间提前声明,再通过 callback、state、feedback、event、expose 等既有契约执行。
默认重复策略
Section titled “默认重复策略”asHook 的默认 repeat policy 是 once。在同一个调用者 prototype setup 链路中,同一个 asHook identity 默认只安装一次;后续同 identity 调用必须跳过,且不应抛错。
这条规则用于避免继承式交互身份被重复安装并产生重复副作用。需要参数合并、latest-wins 或多次安装的 hook 必须由特权 asHook 或后续明确契约单独定义,不属于 asHook 默认语义。
Effects 与归属
Section titled “Effects 与归属”asHook 通过 def 引入的 setup effects 必须归属调用者 prototype:
asHook setup -> def.state / def.event / def.feedback / def.expose / def.rule / ... -> captured setup effects -> attached to caller prototype -> executed by each owning module contractasHook runtime 只负责捕获、归属与暴露结果。每个模块内部的去重、合并、冲突、诊断和执行顺序仍由对应模块契约负责。asHook 不为自身创建独立的 props/state/event/feedback/context/expose/rule 运行域。
asHook 可以依赖 def 句柄上的任意子 API。使用 def.props、def.state、def.event、def.feedback、def.expose、def.context、def.rule、def.lifecycle 或其他 setup API,是正常的 asHook 设计,不构成过度耦合。它与 prototype 遵守同一套 phase 规则:def 声明发生在 setup 期间;asHook 声明的 callback API 之后也可以像 prototype 自己声明的 callback 一样获得 run。
当前实现中的 capture bucket 是实现细节,不是公开语义。契约只描述捕获了哪些 setup effects、暴露哪些 artifacts/disposers/handles,以及这些效果如何归属调用者。
AsHookResult
Section titled “AsHookResult”AsHookResult 是 asHook caller 的返回值。它可以暴露:
| 结果类型 | 语义 |
|---|---|
| handles | asHook 引入并希望调用者消费的稳定 handle |
| artifacts | 可分析产物,例如 event key、method descriptor、style handle |
| render fragment | 由调用者显式组合的可渲染片段 |
| disposers | 用于 setup 期间撤销可撤销 setup effects 的函数 |
disposer 的边界很重要:它用于让调用者在 setup 期间撤销 asHook 引入的 setup effects,不是 runtime cleanup、callback disposer 或 lifecycle disposer。
无法撤销或无需撤销的贡献可以只暴露 handle 或 artifact。例如创建 state slot 通常不需要提供“删除这个 state”的语义;不消费它就相当于取消使用。
State Projection
Section titled “State Projection”asHook 内部创建或引入的可控 state 暴露给调用者时,应投影为 borrowed view,而不是 owned view。
这表示调用者可以读取、设置默认值、在 runtime 设置值,并注册 watcher;但这个 state 的来源仍然是 asHook 引入的可复用逻辑,不是调用者直接声明并完全拥有的 state。
特权 asHook 引入的系统维护事实,也可以暴露为 observed 且 state-backed 的 handle。它们仍应携带标准 state metadata,使 rule、expose 与 diagnostics 能把它们当作 state-shaped value 消费。
interaction state 也遵守这条边界。若 asHook 内部声明 interaction state,它必须归属调用者所在交互主体,而不是成为 asHook 私有 state。
调用者 prototype 必须保留已应用 asHook 的只读 trace metadata。trace 用于诊断、anatomy requirement 能力检查、工具展示与实现调试。
trace 至少应包含:
| 字段 | 用途 |
|---|---|
| identity | 标识应用过的 asHook |
| order | 表达应用顺序 |
| privileged | 标记是否为特权 asHook |
| repeat policy | 暴露已稳定的重复策略信息 |
trace 不是原型作者可写状态,也不是 runtime 行为通路。诊断系统可以读取它,但不得通过 trace 注入行为。
特权 asHook
Section titled “特权 asHook”特权 asHook 是官方提供的 asHook 形态,用于把过于强力、过于依赖宿主,或不适合直接暴露给原型作者的 module port 能力,以受限 API 交给原型作者使用。
典型方向包括 focus、overlay、hit participation、interaction boundary,以及 asTrigger 这类需要移动 event target 或协调多个模块的能力。
特权 asHook 仍然共享普通 asHook 的 def 语法权利。props、state、event、feedback、expose、context、rule、lifecycle 等可以由原型语法表达的部分,应优先通过 def 表达。非公开 module port、facade 或 host capability 应保留给普通原型语法无法表达的部分。
每个特权 asHook 都是独特 API,必须定义自己的:
| 项目 | 说明 |
|---|---|
| 参数 | 是否允许参数、参数是否参与 identity、如何校验 |
| 返回值 | 暴露哪些 handles、artifacts 或受限 facade |
| repeat policy | 是否仍是 once,或采用自定义合并规则 |
| 配置合并 | 多次调用时如何合并、覆盖或诊断冲突 |
| 安全边界 | 哪些能力可以撤销,哪些能力出于安全或宿主约束不能撤销 |
特权 asHook 必须在 trace 中标记 privileged: true。
Deferred Scope
Section titled “Deferred Scope”以下内容已经记录为断口或治理空间,但不属于当前稳定 v0 asHook core:
| 项目 | 状态 |
|---|---|
| caller binding name validation | spec.name 与 asXxx caller binding 分离;后续由 tooling 检查 |
| ordinary configurable authored asHook | 实现已有能力,但参数 identity、配置合并与冲突诊断仍待治理 |
defineHook / defineUseHook | 是否需要独立 use-style hook API 仍待讨论 |
| capture bucket shape | 当前 runtime bucket 是实现细节,不固化为公开语义 |
| 特权 asHook 细则 | 由 focus、overlay、boundary 等二级 scope 分别定义 |
契约实体预览
Section titled “契约实体预览”与测试的关系
Section titled “与测试的关系”asHook 的测试实体按核心行为、结果捕获、特权能力三组组织:
| 测试实体 | 主要覆盖 |
|---|---|
T-AS-HOOK-0001 | subjectless attachment、setup-only caller、prototype-compatible definition、once 默认策略、readonly trace |
T-AS-HOOK-0002 | caller-owned captured effects、AsHookResult artifacts、setup-only disposer、borrowed state projection |
T-AS-HOOK-PRIVILEGED-0001 | 特权 asHook 的 module-port capability、privileged trace、rollback boundary |
当前 runtime 合约测试承接核心 asHook 行为;focus、boundary、overlay、hit-participation 等特权 asHook 测试承接 privileged API 的最低边界。
与其他规范的关系
Section titled “与其他规范的关系”Core定义 prototype setup、render return 与 setup/runtime phase boundary;asHook 复用这些基础语义。State定义 owned、borrowed、observed view;asHook 暴露的 state 投影为 borrowed view。Event、Feedback、Expose、Context、Rule等模块负责自身 effects 的去重、合并、冲突与执行顺序;asHook 只负责让这些 effects 附着到调用者。Anatomy可以读取 asHook trace 来检查 role requirement,但不能通过 trace 注入行为。- 特权 asHook 可以使用普通原型作者 API 不开放的 module port;这些能力需要由各自二级契约继续定义。