lowcode-engine 属性集模型(Props Model)完全指南:IPublicModelProps 属性、方法与实战
【免费下载链接】lowcode-engineAn enterprise-class low-code technology stack with scale-out design / 一套面向扩展设计的企业级低代码技术体系项目地址: https://gitcode.com/GitHub_Trending/lo/lowcode-engine
属性集(Props)是 lowcode-engine 文档模型中承载节点组件属性的核心数据结构。本篇指南以官方 API 文档 props.md 为骨架,结合 Props 实现类 与 Prop 实现类 的源码,系统讲解
IPublicModelProps的全部属性与方法、路径寻址语法、普通属性与扩展属性(extra prop)的区别,以及如何在插件与设置器中安全读写属性值。读完本文,你将能够熟练使用属性集模型完成组件属性的增删改查、嵌套路径操作与 schema 导出。
基本介绍:什么是属性集模型
在 lowcode-engine 中,每个文档节点(Node)都对应一份组件配置,而这份配置的主体就是属性集(Props)。属性集模型IPublicModelProps是节点props字段的面向 Shell 层的公开抽象,定义于 packages/types/src/shell/model/props.ts,其内部实现为 designer 包中的Props类(props.ts)。
从类型定义看,IPublicModelProps继承自IBaseModelProps<IPublicModelProp>,即它管理的是一个由若干IPublicModelProp(单个属性模型)组成的集合。与之配套的类型还包括:
- IPublicModelProp:单个属性的公开模型,提供
getValue/setValue/getAsString等方法; - IPublicModelNode:属性集所属的节点模型。
属性集模型于v1.0.0随 Shell 层模型体系一同发布(@since v1.0.0),并在 v1.1.0 中补充了has与add两个方法(@since v1.1.0)。
从底层实现看,Props类在内部维护一个items: IProp[]数组,并通过@obx.shallow与@computed保持响应式能力——当某个属性的值发生变化时,会通过Prop#emitChange触发GlobalEvent.Node.Prop.InnerChange事件并回调节点自身的emitPropChange(见 prop.ts),从而驱动设计器画布、属性面板的实时联动。
属性(Properties)
IPublicModelProps暴露了三个只读属性,用于标识属性集自身的身份与归属。
id
@type {string}
属性集实例的唯一标识。底层实现中由uniqueId('props')生成(见 props.ts),在文档生命周期内保持稳定,可用于在调试或持久化场景中区分不同的属性集实例。
path
@type {string[]}
返回当前属性集的路径。对于节点根级属性集,其path为空数组[](底层Props类中readonly path = []);对于嵌套属性(如prop.get(...)返回的子级Prop),其path由父级路径拼接当前 key 得到(见 prop.ts)。路径以数组形式表达,便于程序化地追踪属性在 schema 中的位置。
node
@type {IPublicModelNode | null}
返回当前属性集所属的节点实例。Shell 层实现通过ShellNode.create(this[propsSymbol].getNode())将内部节点包装为公开的IPublicModelNode(见 packages/shell/src/model/props.ts);若底层节点不存在,则返回null。这一属性在需要从属性集反查节点、进而操作节点本身(如获取节点componentName、children)时非常有用。
路径寻址语法:a / a.b / a.0
属性集模型的所有方法都围绕**路径(path)**展开,因此先理解路径语法是关键。底层Props#get实现(见 props.ts)对路径的解析规则如下:
- 单段路径:如
a,直接在当前属性集中按 key 查找; - 多段路径:如
a.b,先取第一段a作为入口,剩余b递归下钻; - 数组下标:如
a.0,当某段属性是列表类型(list)时,数字段会被解析为数组下标——Prop#get中通过isValidArrayIndex(entry, this.size)校验并索引(见 prop.ts)。
例如对 schema:
{ "props": { "title": "按钮", "style": { "color": "red" }, "dataSource": [{ "name": "a" }, { "name": "b" }] } }getPropValue('title')、getPropValue('style.color')、getPropValue('dataSource.1.name')分别可命中普通属性、嵌套对象字段和列表元素。
需要特别说明get的第二个参数createIfNone(默认为false):当目标属性不存在且createIfNone为true时,会创建一个值为UNSET的占位Prop并写入集合(见 props.ts);若为false则返回null。setPropValue内部正是利用getProp(path, true)实现"不存在即创建再写入"的语义。
方法(Methods)
getProp
获取指定 path 的属性模型实例。
/** * 获取指定 path 的属性模型实例 * get prop by path * @param path 属性路径,支持 a / a.b / a.0 等格式 */ getProp(path: string): IPublicModelProp | null;返回类型为 IPublicModelProp。Shell 层通过ShellProp.create(...)包装内部 Prop(见 packages/shell/src/model/props.ts)。拿到属性实例后,可继续调用其getValue()/setValue()/getAsString()等方法完成更精细的操作。注意:getProp不会创建不存在的属性,找不到时返回null。
getPropValue
获取指定 path 的属性模型实例值。
/** * 获取指定 path 的属性模型实例值 * get value of prop by path * @param path 属性路径,支持 a / a.b / a.0 等格式 */ getPropValue(path: string): any;这是最常用的读值方法。Shell 层实现为this.getProp(path)?.getValue()(见 packages/shell/src/model/props.ts),底层getValue实际执行export(IPublicEnumTransformStage.Serilize),即返回序列化阶段的值(见 prop.ts)。返回值类型为any:可能是字面量、{ type: 'JSExpression', value: 'state.x' }表达式、对象、数组或 JSSlot 结构。
getExtraProp
获取指定 path 的属性模型实例。
/** * 获取指定 path 的属性模型实例, * 注:导出时,不同于普通属性,该属性并不挂载在 props 之下,而是与 props 同级 * get extra prop by path * @param path 属性路径,支持 a / a.b / a.0 等格式 */ getExtraProp(path: string): IPublicModelProp | null;getExtraPropValue
获取指定 path 的属性模型实例值。
/** * 获取指定 path 的属性模型实例值 * 注:导出时,不同于普通属性,该属性并不挂载在 props 之下,而是与 props 同级 * get value of extra prop by path * @param path 属性路径,支持 a / a.b / a.0 等格式 */ getExtraPropValue(path: string): any;扩展属性(extra prop)是理解属性集的关键概念之一。它用于承载那些不属于组件 props 配置、但与节点生命周期/设计器行为强相关的元数据,例如condition(条件渲染)、loop(循环)、hidden(隐藏)、isLocked(锁定)、title等。在Node构造函数中,这些内置指令正是通过props.add(..., getConvertedExtraKey('condition'))等方式写入属性集的(见 node.ts)。
底层实现中,扩展属性的 key 带有___前缀:getConvertedExtraKey(key)将condition转换为___condition___,而getOriginalExtraKey负责反向还原(见 props.ts)。Shell 层的getExtraProp即先转换 key 再查找(见 packages/shell/src/model/props.ts)。
其"导出时与 props 同级"的语义体现在Props#export中:导出时,key 以___开头的属性会被剥离前缀并归入独立的extras对象,而普通属性留在props对象内(见 props.ts),最终节点 schema 形如:
{ "componentName": "Button", "props": { "children": "确定" }, "condition": true, "loop": { "type": "JSExpression", "value": "state.list" } }其中condition、loop与props同级。因此,读写这类字段必须使用getExtraProp/getExtraPropValue/setExtraPropValue,而不是普通属性方法。
setPropValue
设置指定 path 的属性模型实例值。
/** * 设置指定 path 的属性模型实例值 * set value of prop by path * @param path 属性路径,支持 a / a.b / a.0 等格式 * @param value 值 */ setPropValue(path: string, value: IPublicTypeCompositeValue): void;value的类型为 IPublicTypeCompositeValue,即复合类型,包含:
- 任意 JSON 值(
string/number/boolean/null/ 普通对象 / 数组); IPublicTypeJSExpression(如{ type: 'JSExpression', value: 'this.state.x' });IPublicTypeJSFunction(函数表达式);IPublicTypeJSSlot(插槽,如{ type: 'JSSlot', value: [{ componentName: 'Text' }] });- 由上述类型任意嵌套组成的复合数组/复合对象。
底层Prop#setValue会根据值形态自动判定属性类型:字符串/数字/布尔归类为literal,数组为list,JSSlot为slot,JSExpression为expression,普通对象为map(见 prop.ts)。设置完成后会触发setupItems重建子属性并派发变更事件,保证响应式链路不中断。实测示例见 props.test.ts:props.setPropValue('a', 2)后getPropValue('a')返回2。
setExtraPropValue
设置指定 path 的属性模型实例值(扩展属性版)。
/** * 设置指定 path 的属性模型实例值 * set value of extra prop by path * @param path 属性路径,支持 a / a.b / a.0 等格式 * @param value 值 */ setExtraPropValue(path: string, value: IPublicTypeCompositeValue): void;Shell 层实现先经getConvertedExtraKey转换 key,再调用内部setValue(见 packages/shell/src/model/props.ts)。典型用途如设置节点的condition(条件渲染开关)、loop(循环数据源)等指令级属性。与setPropValue相同,value同样支持 IPublicTypeCompositeValue 复合类型。
has
当前 props 是否包含某 prop。
/** * 当前 props 是否包含某 prop * check if the specified key is existing or not. * @param key * @since v1.1.0 */ has(key: string): boolean;@since v1.1.0。底层实现直接查询内部maps集合(见 props.ts),时间复杂度 O(1)。注意:has只判断 key 是否存在,不校验值是否为UNSET(未设置占位)。在Node初始化指令时,正是用props.has(getConvertedExtraKey('condition'))判断是否需要补默认值(见 node.ts)。
add
添加一个 prop。
/** * 添加一个 prop * add a key with given value * @param value * @param key * @since v1.1.0 */ add(value: IPublicTypeCompositeValue, key?: string | number | undefined): any;@since v1.1.0。key可选:传入时以指定 key 新增属性,不传则由实现决定(如列表场景按序号)。底层Props#add会创建一个新Prop并追加到items,返回该 Prop 实例(见 props.ts),因此可链式调用返回值的setValue等方法。value同样为复合类型。
实战:在插件/设置器中读写属性
将上述 API 组合起来,即可在插件或自定义设置器中完成常见的属性操作。以下示例基于documentModel与节点模型:
// 获取当前选中节点 const node = documentModel.getNode('xxx') || documentModel.selection.getNodes()[0]; const props = node.props; // IPublicModelProps // 1. 读取普通属性与嵌套路径 const title = props.getPropValue('title'); const color = props.getPropValue('style.color'); // 2. 写入属性(不存在则自动创建) props.setPropValue('title', '新标题'); props.setPropValue('style.color', '#ff6600'); // 3. 读取表达式属性(复合值) const expr = props.getPropValue('visible'); // expr => { type: 'JSExpression', value: 'state.visible' } // 4. 操作扩展属性(与 props 同级导出的指令字段) const cond = props.getExtraPropValue('condition'); // true / false / 表达式 props.setExtraPropValue('condition', false); // 让组件在渲染阶段不展示 props.setExtraPropValue('loop', { type: 'JSExpression', value: 'state.list' }); // 5. 判断与新增 if (!props.has('dataSource')) { props.add([], 'dataSource'); // 新增一个空数组属性 } // 6. 通过 getProp 拿到实例做精细操作 const prop = props.getProp('title'); if (prop) { const s = prop.getAsString(); // 字面量转字符串 prop.setValue('又一个标题'); }几点实战建议:
- 优先使用
getPropValue/setPropValue而非先getProp再取值,前者写法更简洁且自带安全防护; - 在设置器(Setter)内部通常通过
SettingTarget操作属性,其底层同样委托到本文所述属性集模型(见 prop.ts 中的getPropValue/setPropValue/clearPropValue注释@see SettingTarget); - 修改属性后无需手动刷新画布,变更事件(
GlobalEvent.Node.Prop.InnerChange)会自动驱动相关视图更新; - 若要移除某属性,可先
getProp(path)拿到实例再调用其unset()(标记为未设置)或remove()(从父级移除),见 prop.ts 与 prop.ts。
与节点模型的关系及内部机制
属性集不是孤立存在的,它与节点模型深度绑定:
- 初始化:
Node构造函数中通过new Props(this, props, extras)创建属性集,其中props与extras来自 schema 解构(const { componentName, id, children, props, ...extras } = nodeSchema,见 node.ts),这正是"扩展属性与 props 同级"在导入方向的体现; - 查询转发:节点上的
getProp/getPropValue/setPropValue/setExtraPropValue等方法均转发给this.props(见 node.ts),因此既可通过node.props也可直接通过节点方法操作属性; - 导出(序列化):
Props#export(stage)依据map/list两种存储形态分别导出为props对象或props数组,并将___前缀的扩展属性剥离到extras(见 props.ts);Prop#export则按literal / expression / slot / map / list五种类型递归导出(见 prop.ts),其中JSSlot在Render阶段还会附加params、id等运行时信息; - 响应式与变更:属性值变更经由
emitChange抛出Node.Prop.InnerChange事件并回调owner.emitPropChange(见 prop.ts),同时Props#import/merge支持整体替换与增量合并,配合@action保证事务一致性。
测试佐证
属性集的行为在仓库中有完整测试覆盖:props.test.ts 验证了getNode、get、getPropValue、setPropValue、嵌套路径(z.z1)、自动创建属性(get('l', true))等核心能力;prop.test.ts 则覆盖单个Prop的setValue、类型判定、unset、remove、export等行为。阅读测试用例是理解属性集边界行为(如UNSET占位、createIfNone语义、列表下标合法性校验)的最佳途径。
小结
属性集模型IPublicModelProps是 lowcode-engine 中连接 schema、文档节点与 UI 面板的枢纽:通过a / a.b / a.0的统一路径语法,配合getProp/getPropValue/setPropValue与扩展属性系列方法,开发者可以精确、安全地操作任意节点的组件配置;理解其底层Props/Prop实现、___前缀的扩展属性机制与复合值类型系统,则能帮助你在插件开发、自定义 Setter、schema 迁移等场景中写出更可靠的低代码扩展代码。
【免费下载链接】lowcode-engineAn enterprise-class low-code technology stack with scale-out design / 一套面向扩展设计的企业级低代码技术体系项目地址: https://gitcode.com/GitHub_Trending/lo/lowcode-engine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考