Elementor 原子构建器 PropValue 详解:v4 元素 JSON 的类型化数据契约与解析机制
【免费下载链接】elementorThe most advanced frontend drag & drop page builder. Create high-end, pixel perfect websites at record speeds. Any theme, any page, any design.项目地址: https://gitcode.com/GitHub_Trending/el/elementor
导读
PropValue 是 Elementor 原子构建器(Atomic Builder)中 v4 元素 JSON 的类型化数据原子单位——无论是settings还是样式props,都以{ "$$type": ..., "value": ... }这样的信封结构持久化。本篇文章以 docs/atomic-builder/fundamentals/prop-value.md 为骨架,结合仓库中 modules/atomic-widgets/prop-types 与 packages/packages/libs/editor-props 的源码实现,系统讲解 PropValue 的信封结构、plain 与 transformable 两种形态、disabled与 null 的语义区分、overridable 组件包装,以及 PHP/TypeScript 双侧的公共 API。读完本文,你将能够在 REST、MCP、导入导出等场景下正确读写元素数据,并理解渲染解析器(Resolver)对 PropValue 的完整处理链路。
PropValue 是什么
在 v4 元素 JSON 中,PropValue 是类型化数据的最小原子单位,表现为一个带有类型判别符的"信封"(envelope):
{ "$$type": "color", "value": "#wc26-gold" }| 字段 | 含义 |
|---|---|
$$type | Prop 类型键(如string、size、color、global-color-variable等),必须与对应 Prop 类型的get_key()返回值一致 |
value | 该类型定义的数据载荷(payload)形状 |
disabled(可选) | 为true时,渲染解析阶段直接跳过该值 |
注意:
$$type是 Prop 的类型判别符,与元素的widgetType/elType(例如e-heading)不是一回事。
关键事实:持久化的元素数据几乎总是使用信封结构,即使是string这样的原始标量也不例外(参见 glossary.md 中对 PropValue 的定义)。这意味着你在保存的文档 JSON 中不会看到裸的"Welcome",而是{ "$$type": "string", "value": "Welcome" }。
何时使用 PropValue
根据原文档,以下三类场景必须直接操作 PropValue:
- 读写元素数据——通过 REST、MCP 或导入导出功能创建/修改
settings与样式props时,必须以信封结构承载数据; - 区分存储形态与渲染形态——读取保存的 JSON 时看到的是信封结构,而前端渲染输出是经过 Transformer 转换后的结果(例如
size渲染为"12px",image渲染为 URL); - 构建 Agent——为 Agent 或 LLM 生成数据时,应输出
{ $$type, value }形式的信封,而不是裸的 CSS 或标量;示例中使用**标签(label)**而非内部 ID(如用wc26-gold引用全局类,而非内部g-*ID)。
核心概念一:可转换信封(Transformable Envelope)
PHP 侧校验
PHP 端的所有 Prop 类型通过 traitHas_Transformable_Validation实现信封结构校验,源码位于 modules/atomic-widgets/prop-types/concerns/has-transformable-validation.php。其校验逻辑为:
- 值必须是数组;
- 必须同时存在
$$type与value两个键; $$type必须等于当前类型的get_key();disabled键要么不存在,要么必须是布尔值。
TS 侧校验
TypeScript 端对应函数为isTransformable(),实现在 packages/packages/libs/editor-props/src/utils/is-transformable.ts,基于@elementor/schema的 Zod 模式:
import { z } from '@elementor/schema'; const transformableSchema = z.object( { $$type: z.string(), value: z.any(), disabled: z.boolean().optional(), } ); export const isTransformable = ( value: unknown ): value is TransformablePropValue => { return transformableSchema.safeParse( value ).success; };它不仅是类型守卫(type guard),同时也作为运行时校验器,被 editor-props 的其他工具复用(例如is-overridable.ts就基于它判断overridable包装)。
生成信封
在 PHP 中生成信封统一使用工厂方法:
Some_Prop_Type::generate( $inner_value ); // 正常信封 Some_Prop_Type::generate( $inner_value, true ); // 第二个参数传 true 设置 disabled该能力来自Has_Generatetrait(modules/atomic-widgets/prop-types/concerns/has-generate.php),其实现等价于:
public static function generate( $value, $disable = false ): array { $value = [ '$$type' => static::get_key(), 'value' => $value, ]; if ( $disable ) { $value['disabled'] = true; } return $value; }仓库测试用例可以印证这一点,例如 test-icon-prop-type.php 中嵌套使用String_Prop_Type::generate()构造图标值:
$prop_type->validate( Icon_Prop_Type::generate( [ 'value' => String_Prop_Type::generate( 'fas fa-star' ), 'library' => String_Prop_Type::generate( 'fa-solid' ), ] ) );核心概念二:Plain 与 Transformable 的区别
| 维度 | Plain | Transformable |
|---|---|---|
| 形状 | 裸标量(raw scalar) | { $$type, value [, disabled] }信封 |
| 校验 | 仅类型检查 | 信封结构 + 内部validate_value() |
| 渲染 | 直通(passthrough) | Render_Props_Resolver+ Transformer 转换 |
关键点:原始标量类型(string/number/boolean)在存储时依然使用信封。这一点从 modules/atomic-widgets/prop-types/base/plain-prop-type.php 的validate()实现可以确认——它要求值满足is_transformable()(信封结构),再对value['value']调用抽象的validate_value():
public function validate( $value ): bool { if ( is_null( $value ) || ( $this->is_transformable( $value ) && empty( $value['value'] ) ) ) { return ! $this->is_required(); } return ( $this->is_transformable( $value ) && $this->validate_value( $value['value'] ) ); }该基类的get_type()返回static::$KIND(如string、number、boolean),与get_key()共同构成类型判别体系。以 string-prop-type.php 为例,它还提供了enum()与regex()约束能力(存入settings['enum']/settings['regex']),并在validate_value()中一并校验。
核心概念三:disabled的语义
disabled表示"渲染时抑制",与"重置"是完全不同的概念。其核心行为:
Render_Props_Resolver::resolve_item()遇到disabled: true时返回null,即不产生任何 CSS/输出;- 值本身可以继续持久化并在编辑器中展示;
- Transformer 可以通过
Props_Resolver_Context::is_disabled()读取该状态,实现条件化转换。
在 modules/atomic-widgets/props-resolver/render-props-resolver.php 中可以看到确切的实现顺序(先于转换执行):
if ( isset( $value['disabled'] ) && true === $value['disabled'] ) { return null; } $transformed = $this->transform( $value, $key, $prop_type );上下文对象 props-resolver-context.php 提供了set_disabled()/is_disabled()及set_key()/get_key()、set_prop_type()/get_prop_type()等链式构造方法(make()静态工厂),供 Transformer 在转换时查询当前 Prop 的元信息。
仓库测试 test-render-props-resolver-array-prop-type.php 直接验证了这一行为:在 transition 数组的三个条目中,第二个条目通过Selection_Size_Prop_Type::generate( $item, true )(第二参数true即GENERATE_ITEM_DISABLED)标记为 disabled,解析后输出为'all 200ms, all 400ms'——disabled 条目被跳过,未参与 CSS 拼接。
核心概念四:Null 与重置语义
| 场景 | 含义 |
|---|---|
| 键缺失(key absent) | 使用该 Prop 类型的默认值(Prop_Type::get_default()) |
值null | 显式重置(explicit reset),从净化后的输出中省略 |
value对象内部的null叶子 | 仅重置对象中的单个子字段(部分重置) |
disabled: true | 渲染时抑制(与重置不同) |
值得注意的细节(来自 render-props-resolver.php 的get_validated_value()):当某个 prop 的值为null时,解析器会回退到$prop_type->get_default();当值缺失键时,resolve()中$props[ $key ] ?? null同样会进入默认值分支。
CSS 转换器(Style_Props_To_Css)将顶层null与部分为null的对象都视为重置信号——从源码 style-props-to-css.php 可以看到,最终输出会array_filter掉所有null与空字符串,确保重置值不会产生残留 CSS。更完整的重置语义与"部分 null 绕过校验"机制详见 validation.md:Css_Converter::validate_props()会把顶层null/ 含 null 叶子的对象归为"Null resets"单独处理,cleanup_props()还会把全 null 对象折叠为顶层null。
核心概念五:Overridable 组件包装
组件实例(component instance)会把设置包装进overridable信封,例如:
{ "$$type": "overridable", "value": { "override_key": "hero-title", "origin_value": { "$$type": "string", "value": "Welcome" } } }该包装由Overridable_Transformer解析——它根据渲染上下文中的override_key从实例的 overrides 中取出覆盖值替换origin_value。完整的组件实例与覆盖机制(component-instance、override、overrides四种 Prop 类型的形状、Overridable_Schema_Extender的挂载方式、InstanceEditingPanel覆盖编辑 UI、detach 展开等)参见 instances-and-overrides.md。
在 TS 侧,判断与重包装工具位于 packages/packages/libs/editor-props/src/utils/is-overridable.ts:
isOverridable( value ):isTransformable( value ) && value.$$type === 'overridable',用于检测 overridable 包装;rewrapOverridableValue( prop, newValue ):依赖级联(dependency cascade)后把新的内部值重新包装进origin_value,同时保留原有override_key:
export function rewrapOverridableValue( existing: OverridableTransformable, newInner: TransformablePropValue< string > ): OverridableTransformable { return { ...existing, value: { ...existing.value, origin_value: newInner, }, }; }核心概念六:公共 API 速查表
| 符号 | 签名 | 用途 | 源码位置 |
|---|---|---|---|
Transformable_Prop_Type::generate() | static generate( $value, $disable = false ): array | PHP 侧构造信封 | has-generate.php |
isTransformable() | isTransformable( value: unknown ): value is TransformablePropValue | TS 侧校验信封 | is-transformable.ts |
createPropUtils().create() | create( value, options? ): Prop | TS 侧构造信封 | create-prop-utils.ts |
isOverridable() | isOverridable( value ): boolean | 检测 overridable 包装 | is-overridable.ts |
rewrapOverridableValue() | rewrapOverridableValue( prop, newValue ) | 依赖级联后重新包装 | is-overridable.ts |
createPropUtils()的完整能力
在 TypeScript 侧,create-prop-utils.ts 为每种 Prop 类型生成一个工具对象,内部使用 Zod 的strictObject校验{ $$type, value, disabled },返回{ create, extract, isValid, schema, key }:
const elementsPropUtils = createPropUtils( 'elements', z.array( z.string() ) ); elementsPropUtils.isValid( element.props?.children ); // 校验 elementsPropUtils.create( [ 'a', 'b' ] ); // 直接构造 elementsPropUtils.create( ( prev = [] ) => [ ...prev, 'c' ], { base: element.props?.children } ); // 基于旧值更新 elementsPropUtils.create( ( prev = [] ) => [ ...prev, 'c' ], { disabled: true } ); // 构造 disabled 值 elementsPropUtils.extract( element.props?.children ); // 解包取 value(无效返回 null)注意其语义细节:create()支持函数式更新器(updater)模式,配合base选项可在旧值基础上派生新值;base无效时会抛出Cannot create prop based on invalid value错误;disabled选项仅在为真时写入disabled键。工具对象还会按 key 缓存到SCHEMA_CACHE,供getPropSchemaFromCache()复用。
扩展:如何接入新的类型化数据
PropValue 是原子构建器类型体系的一环,若要扩展,请遵循三步流程(详见 prop-types.md 与 transformers.md):
- 创建 Prop 类型(PHP + TS)——PHP 端在
modules/atomic-widgets/prop-types/下新建类,继承Plain_Prop_Type(或Object_Prop_Type/Array_Prop_Type/Union_Prop_Type)并实现get_key()、validate_value()、sanitize_value();TS 端在packages/packages/libs/editor-props/src/prop-types/下用createPropUtils()建立镜像; - 注册 Transformer(如果渲染输出不同)——在对应上下文的注册 hook 中注册:
elementor/atomic-widgets/settings/transformers/register(settings)、.../styles/transformers/register(样式)、.../import/...与.../export/...(导入导出); - 在 schema 中引用——通过
define_props_schema()或elementor/atomic-widgets/props-schema过滤器挂载:
protected static function define_props_schema(): array { return [ 'badge_label' => String_Prop_Type::make()->default( 'New' ), ]; }经验法则:校验与 JSON Schema 导出交给 Prop 类型;存储形状正确但渲染表示不同(如size→"12px"、image→ URL)时才需要 Transformer——判断依据见 transformers.md 中"Transformer vs prop type"对照表。
内部实现要点(Internals)
Plain_Prop_Type::validate()的空值宽松:当值不是 required 时,null或空value的信封均视为合法(见 plain-prop-type.php 的validate()首行判断);Render_Props_Resolver::resolve_item()的深度限制:TRANSFORM_DEPTH_LIMIT = 3,Transformer 返回的仍为可转换值时继续递归转换,超过深度返回null,防止无限循环(见 render-props-resolver.php 第 24 行常量与resolve_item()实现);resolve()的多键输出:当 Transformer 返回Multi_Props包装时,解析结果会被展开合并进结果数组(Multi_Props::is()/Multi_Props::get_value()),使单个 Transformer 能扩展为多个 CSS 键。
与其他文档的关联
- prop-types.md——Prop 类型分类法(plain/object/array/union)、内置领域类型清单与 JSON Schema 导出;
- validation.md——双层校验(PHP
Props_Parser+ TSvalidatePropValue)与 null 重置语义的完整处理; - instances-and-overrides.md——
overridable/override信封的组件实例上下文; - binding-propvalues.md——
$$type: "dynamic"的动态标签绑定,同样是标准 PropValue 信封; - glossary.md——label 与内部 id 的区别、原子元素与遗留 widget 的判定等术语速查。
【免费下载链接】elementorThe most advanced frontend drag & drop page builder. Create high-end, pixel perfect websites at record speeds. Any theme, any page, any design.项目地址: https://gitcode.com/GitHub_Trending/el/elementor
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考