Props
Props 规范
Props 是什么?
Section titled “Props 是什么?”Props 是 Proto UI 的信息通路之一,承载 App Maker 流向 Component 的配置输入。它让外部调用者能够告诉组件:这一次调用应该使用哪些参数。
如果没有 Props 通路,App Maker 几乎无法主动向组件传输数据。组件仍然可以拥有内部状态、响应用户事件、向外暴露能力,但外部调用者无法稳定地配置它。
这条通路不是 React props、Vue props 或 Web Component attributes 的简单统一命名。不同宿主可以用不同方式接收输入,但进入 Proto UI 之后,Props 需要遵守同一套声明、解析、读取和监听语义。
Props 的执行时期
Section titled “Props 的执行时期”Props API 按 setup 和 runtime 两个时期划分。
setup 期用于描述组件的可配置面:定义 props 的形状、设置默认值、注册监听器。此时真实调用尚未发生,所以不能读取本次调用传入的 props。
runtime 期用于读取本次调用的 props 值。此时组件已经进入实际运行,不能再定义新的 props 形状,也不能新增 watcher。Proto UI 禁止 runtime 期引入这类结构性副作用。
可以把 Props 理解成这条链路:
raw props -> declared prop shape -> resolution -> resolved props -> runtime read / watcher callback定义 Props
Section titled “定义 Props”def.props.define() 用于声明组件接受哪些 props,以及每个 key 的值应该如何被解释。它定义的是组件的可配置面,不是给组件写入一次运行时数据。
type TooltipProps = { disabled: boolean; side: 'top' | 'right' | 'bottom' | 'left'; delay: number;};
def.props.define<TooltipProps>({ disabled: { type: 'boolean', default: false }, side: { type: 'enum', options: ['top', 'right', 'bottom', 'left'], default: 'bottom', }, delay: { type: 'number', range: { min: 0, max: 1000 }, default: 100 },});同一个原型可以多次调用 define()。这些声明会被合并,而不是简单地以后一次覆盖前一次。可兼容的声明可以共同形成最终 props shape;不可兼容的声明会被拒绝并抛错。具体合并规则可查 C-PROPS-0007。
Props value 只支持 JSON value。string、number、boolean、null、array 和 plain object 可以成为 portable props value;函数、日期、正则表达式、DOM object、class instance 等不能成为 portable props value。default 也必须满足同样的 JSON value 边界。
监听 Props 变化
Section titled “监听 Props 变化”def.props.watch() 用于注册 props 变化监听器。它观察的是 resolved props 的变化,而不是每一次 raw props 传入动作。
def.props.watch(['disabled'], (run, next, prev, info) => { if (next.disabled !== prev.disabled) { run.update(); }});重新传入 props 不会自动导致 UI 更新。它只会更新 Props 通路中的数据,并在相关值真的发生变化时触发 watcher。是否发生 render commit 属于更上层 runtime/update 语义,不由 Props 通路隐式决定。
watcher 的触发按 key 独立判断。连续两次传入相同的 props,不应该触发 callback。key a 发生变化,不会触发只监听 key b 的 callback。一个 watcher 可以同时监听多个 key,只要其中任意一个被监听的 key 发生变化,这个 watcher 就会触发。
def.props.watchAll() 可以监听所有 declared props 的 resolved value 变化。初始 hydration 不触发 watcher;watcher 只响应后续变化。
获取 Props 的值
Section titled “获取 Props 的值”run.props.get() 返回当前 runtime invocation 的 resolved props snapshot。resolved props 是经过声明和 resolution 处理后的结果:
- 只包含声明过的 key。
- 未声明但传入的 key 会被丢弃。
- 不暴露
undefined。 - 空结果使用
null。 - 非法值会根据该 key 的
empty/ fallback 规则处理。
const props = run.props.get();
if (!props.disabled) { // 根据本次调用的 resolved props 执行逻辑。}run.props.isProvided(key) 用来判断某个 key 是否在当前 raw props 中被显式提供过。它回答的是“调用者有没有传这个 key”,不是“resolved value 是否非空”。
raw props 和 resolved props 都应该被视为不可修改的数据快照。修改这些对象没有协议意义,也不应该被用来传递状态变化。
用于诊断的 Raw Props API
Section titled “用于诊断的 Raw Props API”run.props.getRaw()、def.props.watchRaw() 和 def.props.watchRawAll() 属于 raw props escape hatch。它们暴露的是 adapter 提交给 Props module 的 pre-resolution snapshot,适合诊断、调试、兼容宿主差异,或在极少数情况下作为逃生舱使用。
raw props 不等于宿主原始输入的绝对全量。React、Vue、Web Component 对 props、attrs、listeners 和 DOM attributes 的分流方式并不相同,adapter 可以决定哪些宿主输入会进入 raw props。
Raw API 不扩展 portable props 语义。raw props 可以包含未声明 key,也可能包含宿主相关值;依赖这些值会削弱跨平台兼容性。正式原型逻辑应优先依赖 resolved props。
契约实体预览
Section titled “契约实体预览”与测试的关系
Section titled “与测试的关系”Props 的测试映射主要落在 spec/tests/T-PROPS-0001..0011.yaml,并由 module、runtime 与 adapter conformance 测试共同覆盖。
| 测试实体 | 主要覆盖 |
|---|---|
T-PROPS-0001 | JSON props value boundary |
T-PROPS-0004 | declaration merge safety 与 failed merge atomicity |
T-PROPS-0005 | resolved snapshot shape |
T-PROPS-0006 | empty / invalid fallback resolution |
T-PROPS-0007 | resolved watcher diff、coalescing、dispatch order |
T-PROPS-0008 | raw props escape hatch 与 raw watcher |
T-PROPS-0011 | applyRawProps runtime/controller integration |
这些测试不只是实现回归测试,也是在把 YAML 契约实体变成可执行的行为边界。
与其他规范的关系
Section titled “与其他规范的关系”Core定义 channel、value 与执行时期等共通基础;Props 是其中一条具体通路。State表达组件在时间中的内部连续性;Props 表达外部配置输入。Event表达 user-to-component 的交互输入;Props 表达 maker-to-component 的配置输入。Context更适合组件间共享环境;Props 更适合显式、局部、由使用者传入的配置。- runtime/update 仍需承接
run.update()与 render commit 的正向调度规则;C-PROPS-0014只明确 props update 本身不隐含 commit。