Props 与 Slot
前置:核心概念;本页:Slot 类型、修饰符与数据绑定参考。
在 PurePHP 中,“props”有两种形式:
- 静态 props——构建组件时已知的值(函数参数、字面量属性)。
- 动态 props——渲染时绑定的值:
Slot占位符。
本页是数据绑定参考;渲染管线本身请参见编译渲染。
静态 props
HTML 属性
属性通过方法链式调用设置,并以字面量形式存储在 Shape 中:
<?php
use Pure\Compile\Compile;
use function Pure\HTML\div;
$shape = Compile::shape(
div('Content')
->id('main')
->class('container')
->style('background: #fff;')
);
$shape([]);className() 是 class() 的别名,并且可以向 class() 传入多个值:
<?php
use function Pure\HTML\div;
div('Content')->class('container', 'mt-4')->id('main');数据属性与 ARIA 属性
包含连字符的属性名使用下划线,因为 - 在 PHP 方法名中无效:
<?php
use function Pure\HTML\div;
div('Content')
->data_id('123')
->data_type('card')
->aria_label('Card');布尔属性
值为 true 时,属性以自身名称作为值渲染;false 与 null 则省略该属性:
<?php
use function Pure\HTML\input;
input()->type('checkbox')->checked(true); // checked="checked"
input()->type('checkbox')->checked(false); // no checked attribute后续用 false 或 null 调用 setter 会被忽略,并不会删除之前已经设置的属性。例如 input()->checked(true)->checked(false) 仍会渲染 checked="checked"。如果后面的值 必须清除属性,请一次设置最终值,或重新创建标签。
Slot::value() 在渲染时遵循同样的规则,因此静态属性与动态属性不会出现语义偏差:绑定的 false 省略该属性,绑定的 true 渲染为 checked="checked"。
动态 props
动态属性值使用 Slot::value()。参数是数据键而不是属性名——属性名来自 setter,因此 ->class(Slot::value('classList')) 会从数据中取 classList 并写入 class。null 值会在渲染时省略该属性(绑定的 false 行为相同),条件属性也可以通过这种方式实现:
<?php
use Pure\Compile\Compile;
use Pure\Core\Slot;
use function Pure\HTML\button;
$shape = Compile::shape(
button('Save')->class(Slot::value('classList'))->disabled(Slot::value('disabled'))
);
$shape(['classList' => 'btn btn-primary', 'disabled' => null]);
// <button class="btn btn-primary">Save</button>
$shape(['classList' => 'btn btn-primary', 'disabled' => 'disabled']);
// <button class="btn btn-primary" disabled="disabled">Save</button>Slot 参考
| Slot | 值 | 行为 |
|---|---|---|
Slot::value($name) | 可字符串化;只有可选值或属性位才接受有效的 null | 位置决定语义:子节点位转义为文本(true 为 "1";子节点/文本位的必填值 Slot 拒绝 null);属性位遵循 setAttr()(true 渲染 name="name",false/null 省略该属性) |
Slot::raw($name) | 可字符串化值,或这类值的可迭代集合;只有可选 Slot 才接受 null | 原样输出,绝不转义;集合按顺序拼接;必填 raw Slot 拒绝 null |
Slot::child($name, $shape) | 数组 | 为 $shape 创建嵌套作用域;即使 Slot 可选,值仍必须是数组 |
Slot::each($name, $shape) | 条目的可迭代集合 | 逐项渲染 $shape;即使 Slot 可选,值仍必须可迭代 |
Slot::if($name, $then, $else = null) | 真值判断 | 渲染分支;缺失的键为 false |
选择列表 Slot 时,看标记是否已经渲染:Slot::raw() 拼接已渲染的标记,Slot::each() 则按数据项逐个渲染 item Shape。
列表项通常是一个作用域,即提供条目 Shape 所读各键的数组。当条目 Shape 恰好渲染一个 Slot 时,该项也可以直接是这个 Slot 的值,字符串列表不必再包一层:
<?php
use Pure\Compile\Compile;
use Pure\Core\Slot;
use function Pure\HTML\{li, ul};
$list = Compile::shape(ul(Slot::each('items', li(Slot::value('label')))));
$list(['items' => ['a', 'b']]); // 两种写法渲染结果相同
$list(['items' => [['label' => 'a'], ['label' => 'b']]]);条目 Shape 读取多个 Slot、把唯一的 Slot 读作嵌套的 Slot::child() 或 Slot::each()、或只读作条件,或者根本不读 Slot 时,都没有单一的值可绑定,其条目仍是 作用域;此时传入标量,错误信息会列出该 Shape 期望的键。
修饰符
<?php
use Pure\Compile\Compile;
use Pure\Core\Slot;
use function Pure\HTML\div;
$userShape = Compile::shape(div(Slot::value('name')));
$itemShape = Compile::shape(div(Slot::value('label')));
Slot::value('subtitle')->required(false); // 键缺失时渲染为空
Slot::value('subtitle')->default('—'); // 键缺失时的回退值
Slot::child('user', $userShape)->default([]);
Slot::each('items', $itemShape)->default([]);required(false)使值 Slot 与 raw Slot 可选;键缺失与显式null会使用空值/回退值。 必填的值 Slot 与 raw Slot 在子节点/文本位既不接受缺失的键,也不接受显式null。 属性位的Slot::value()遵循setAttr(),会省略null;raw Slot 不能用作属性值。- 对
child与each,修饰符只控制缺失键的回退,不改变容器类型:child 的有效值仍必须是 数组,each 的有效值仍必须可迭代。因此default(null)在渲染该分支时仍会失败;希望 留空时请使用default([])(或其它合法容器)。静态分析生成的 docblock 仍可能把可选容器 标为|null,这只描述缺失数据,不会改变运行时的类型检查。 default($value)为缺失的键提供回退值,并使 Slot 可选。回退值会被内联进编译后的渲染器,因此必须是值类型:null、标量或由值类型组成的数组。Slot::if()会以LogicException拒绝这两个修饰符:它的条件是真值判断,回退为false。
值转换与转义
值 Slot 与 raw Slot 接受标量和 Stringable 对象——包括 Raw,它不需要强制转换——可选的值/raw Slot 也会接受有效的 null 回退。使用前会先转换为字符串;数组和其他对象会抛出 InvalidArgumentException,并在信息中给出完整 Slot 路径。raw Slot 更进一步,还接受可字符串化值的可迭代集合,并按顺序拼接它们;元素不可字符串化时会报告偏移,例如 slot 'items[2]' must be stringable。即使 Slot 可选,child 与 each 的值仍会做类型检查:child 必须是数组,each 必须可迭代。
Slot::value()在子节点位使用htmlspecialchars(..., double_encode: false)转义,因此你已经转义过的实体(©)会保持不变。Slot::value()在属性位使用double_encode: true转义。Slot::raw()不执行任何转义——请仅对受信任的标记使用。- 无效的 UTF-8 会被替换为替换字符,而不是产生损坏的输出。
转义保护的是文本与属性语法,并不是完整的 URL 或脚本策略。请校验 href/src 的协议, 不要绑定不可信的 on* 事件或 style 属性,限制动态标签名;页面包含 Raw 标记时,还应 配置合适的 Content Security Policy。
缺失数据
必填 Slot 会抛出带完整路径的 Pure\Core\MissingSlotException。错误信息让拼写错误可见: 它会建议最接近的已提供键名,或列出该作用域实际提供的键;必填的值 Slot 与 raw Slot 显式传入 null 时也会失败(属性 Slot 仍然按 null 省略自身):
<?php
use Pure\Compile\Compile;
use Pure\Core\MissingSlotException;
use Pure\Core\Slot;
use function Pure\HTML\div;
$shape = Compile::shape(div(Slot::value('title'), Slot::value('body')));
try {
$shape(['titel' => 'x', 'body' => 'b']);
} catch (MissingSlotException $error) {
// slot 'title' is required but was not provided; did you mean 'titel'?
echo $error->getMessage(), PHP_EOL;
}
try {
$shape(['title' => null, 'body' => 'b']);
} catch (MissingSlotException $error) {
// slot 'title' is required but was null.
echo $error->getMessage(), PHP_EOL;
}
try {
$shape([]);
} catch (MissingSlotException $error) {
// slot 'title' is required but was not provided.
echo $error->getMessage(), PHP_EOL;
}路径用于标识嵌套作用域:card.title 表示 Slot::child() Slot,items[].title 表示列表项。
派生 props
子组件从其 Slot 名对应的嵌套数据中读取 props,因此请在数据层完成派生,再交给渲染:
<?php
use Pure\Compile\Compile;
use Pure\Core\Slot;
use function Pure\HTML\{div, span};
$badge = Compile::shape(span(Slot::value('label'))->class('badge'));
$shape = Compile::shape(div(Slot::child('user', $badge)));
$shape(['user' => ['label' => 'ADA']]); // <div><span class="badge">ADA</span></div>嵌套 shape 也可以是裸标签树——Slot::child('user', span(Slot::value('label'))) 同样可行; 只有需要单独构建并复用嵌套树时才要写 Compile::shape()。
Slot::each() 同理:每个元素本身就是该项的作用域,所以控制器先把原始行整理成 props 数组列表再渲染;条目 Shape 只渲染一个 Slot 时,直接传原始值也可以。
组件 props 契约
由于 Shape 不含数据,组件的数据契约就存在于它的 Slot 中。请在组件旁边记录该契约,并把绑定数组集中放在一处;渲染时缺失必填键会带完整路径明确报错。