Expose
Expose 规范
Expose 是什么?
Section titled “Expose 是什么?”Expose 是 Proto UI 的 Component → App Maker 信息通路。它让组件向调用者承诺:这个组件实例会向外提供哪些值、状态、方法或 outward signal。
这里的调用者是 App Maker,不是 User。User 能感知组件依赖 Feedback;App Maker 配置组件依赖 Props;App Maker 反过来访问组件承诺提供的能力,才是 Expose。
对习惯 React 或 Vue 的开发者,可以把 Proto UI 的 Props + 部分 Expose 粗略理解成 React/Vue 中常见的 props/ref/event callback 组合。但 Proto UI 会刻意拆开方向:Props 是 App Maker → Component,Expose 是 Component → App Maker。
Expose 也不是简单的 ref API。ref 是某些宿主里访问实例的承载方式;Expose 是组件协议的一条正式通路。adapter 可以用 ref、method、CustomEvent、message、host handle 或其他机制来承载它,但不能改变它的语义方向。
Setup Declaration 与 Runtime Surface
Section titled “Setup Declaration 与 Runtime Surface”Expose registration 是 setup-only。
setup-onlydef.expose.value('version', '1.0.0');def.expose.method('focus', () => focusInternalTarget());
const open = def.state.bool('open', false);def.expose.state('open', open);
def.expose.event('change', { payload: 'json' });这些 API 声明的是组件实例的 outward capability surface,不是一次 render output,也不是一次 runtime 写入。setup 结束后,组件不能动态新增、删除或替换 expose entry。
runtime 期间可以使用已经声明的 surface:
runtime-onlydef.lifecycle.onMounted((run) => { run.expose.emit('change', { open: true });});run.expose.emit 只适用于已经通过 def.expose.event 声明过的 outward signal。它不会重新注册 expose entry,也不是 Event 通路的 runtime API。
exposed state 也可以在 runtime 期间反映内部 state 变化,但变化的是 state slot 的当前值,不是 expose registry 的结构。
Exposes Record 与 Key Namespace
Section titled “Exposes Record 与 Key Namespace”每个组件实例拥有一份 exposes record。每个 entry 由非空 string key 标识:
def.expose.value('label', 'Submit');def.expose.method('reset', () => reset());所有 expose 分类共享同一个 key namespace。某个 key 一旦用于 value、state、method 或 outward signal,就不能再用于另一类 expose entry:
def.expose.value('ready', true);def.expose.event('ready'); // invalid: duplicate keyadapter 必须让 App Maker 能按 key 访问已注册 exposes,或读取完整 exposes record。App Maker 取得的 record 不应是可直接篡改内部 registry 的 live mutable object;修改 snapshot 不能改变组件内部 expose registry。
四类 Expose Surface
Section titled “四类 Expose Surface”Expose v0 将 outward surface 分为四类。
| Surface | 原型作者 API | 面向 App Maker 的语义 |
|---|---|---|
| value | def.expose.value(key, value) | 可读取的 outward information |
| state | def.expose.state(key, handle) | internal state 的 external state handle |
| method | def.expose.method(key, fn) | App Maker 可触发的组件能力 |
| outward signal | def.expose.event(key, spec?) | 组件在 runtime 主动广播的 signal |
Expose value 适合常量、静态配置、内部描述或不需要变更通知的只读信息。
def.expose.value('version', '1.0.0');def.expose.value('features', { dismissible: true });v0 暂不要求所有 expose value 都满足 JSON value boundary。因此暴露 mutable object 或 host-local object 时,不能自动假设它具备跨 host 可移植性、订阅语义或 reactive update 语义。
如果 App Maker 需要知道值何时变化,组件应暴露 state、outward signal,或显式暴露具备订阅语义的 API,而不是依赖外部观察 mutable value。
Expose state 是 Expose 与 State 的交叉模块。组件可以把内部 state 暴露为 App Maker 侧 external state handle:
const open = def.state.bool('open', false);def.expose.state('open', open);App Maker 拿到的不是裸 state slot,也不是原型作者侧 owned、borrowed 或 observed view。它是 adapter/translation-layer 定义的 external view。
external state handle 至少提供:
| API | 语义 |
|---|---|
get() | 读取当前值 |
subscribe(cb) | 订阅 StateEvent-like 变化 |
unsubscribe(off) | 取消订阅,或使用等价机制 |
spec | 只读 StateSpec metadata |
external handle 不提供 set、setDefault 或其他 App Maker 写入能力。App Maker 可以读取和订阅,但不能直接改写组件内部 state slot。
Method
Section titled “Method”Expose method 表示 App Maker 可以触发的组件能力,例如 focus()、reset()、open() 或 close()。
def.expose.method('focus', () => { focusInternalTarget();});在 JavaScript/Web 宿主中,method 通常会表现为可调用函数。但契约并不要求所有宿主都必须用直接函数引用实现。adapter 可以把它翻译为 ref method、message invocation、command handle 或宿主更自然的调用 surface,只要不改变“App Maker 触发组件能力”的语义。
Outward Signal
Section titled “Outward Signal”def.expose.event 声明组件可能向 App Maker 主动广播某个 outward signal:
def.expose.event('change', { payload: 'json' });
def.lifecycle.onMounted((run) => { run.expose.emit('change', { checked: true });});这里的 event 是 expose surface 的现有 API 名称,不是 User → Component 的 Event 通路。Event 让组件接收 User 交互;expose event 让组件向 App Maker 发出 signal。
v0 payload spec 只提供最小语义提示:
| payload | 语义 |
|---|---|
void | signal 不应依赖 payload |
json | payload 具有 JSON-compatible 意图,但完整 validation 仍未固化 |
any 或缺省 | 不承诺强 portable payload boundary |
options 当前保留给 adapter 或未来扩展,不应被解释为 portable 行为保证。
Adapter Surface
Section titled “Adapter Surface”Expose 的宿主承载方式由 adapter 决定。
Web Component adapter 可以提供 getExposes() snapshot;React/Vue adapter 可以通过 ref、component instance、callback 或框架惯用方式承载;非 Web 宿主也可以映射为 message、command 或 native handle。
adapter 的自由度在承载方式,不在语义方向。它不能把 Expose 改解释为 User feedback、Props input、Event callback 或组件间 Context。它也不能让 App Maker 通过修改 returned record 直接篡改内部 expose registry。
Lifecycle
Section titled “Lifecycle”Expose entries 绑定到组件实例生命周期。
实例存活期间,已注册 expose entry 必须能通过 adapter surface 访问。实例 unmount/dispose 后,exposes 必须被清理、失效,或以可区分方式表示不可用。
这条规则同样适用于更具体的 expose surface:
- dispose 后读取 expose registry 不得继续返回旧的有效 registry。
- dispose 后旧 snapshot 不得被解释为当前 live instance exposes record。
- expose-state external handle 必须失效,并清理 external subscriptions。
- expose-event emit 只在实例存活期间有效。
契约实体预览
Section titled “契约实体预览”与测试的关系
Section titled “与测试的关系”Expose 的测试映射分为本域、outward signal 与 state 交叉模块:
| 测试实体 | 主要覆盖 |
|---|---|
T-EXPOSE-0001 | channel identity、setup-only registration、shared key namespace、record snapshot、value/method、lifecycle |
T-EXPOSE-EVENT-0001 | expose event declaration、runtime emit、unregistered key failure、payload spec |
T-EXPOSE-STATE-0001 | external state handle shape、same-source updates、read-only boundary、lifecycle safety |
这些测试把 Expose 从“拿到一个 ref”收敛为一组可执行的协议边界:谁能访问、什么时候声明、key 如何冲突、每类 surface 代表什么、生命周期结束后如何失效。
与其他规范的关系
Section titled “与其他规范的关系”Core定义信息通路、setup/runtime 与 component/prototype 基础;Expose 是其中 Component → App Maker 的通路。Props是 App Maker → Component 的配置输入;Expose 是反方向的组件能力承诺。Feedback是 Component → User;Expose 不负责 User 可感知呈现。Event是 User → Component;expose.event虽然沿用 event 名称,但语义上是 Component → App Maker outward signal。State定义内部状态机;Expose-state把内部 state 投影成 App Maker 侧只读 external state handle。Lifecycle定义 expose registry、external handle、subscription 与 emit 在 dispose 边界上的失效规则。