我们到底在折腾什么?
可视化搭建工具里,最烦人的就是深层属性的修改。比如改一个图片的边框宽度,路径可能是 style.border.width,老办法是把整个 style 对象读出来,改完再整个塞回去。一不小心就把别的属性弄丢了,性能也烂,整个组件树跟着重绘。
后来我们想,能不能做到:改哪个叶子,就只动那一个叶子,跟它隔壁的属性井水不犯河水。而且,怎么让配置(这个属性怎么编辑)和真实数据(用户填的值)彻底分家,各自清爽。
于是就有了下面这套思路。
核心想法:两棵长得一样的树
整个系统里其实有两棵对象树,结构完全一致:
- 配置树(Config Tree) :负责告诉编辑器"这个属性长什么样"------是输入框、下拉、颜色选择器?是不是只读?有没有最大值限制?联动规则是啥?
- 真实值树(Value Tree) :只存用户填进去的实实在在的数据,没有任何多余的东西。
它们的区别只有一点:配置树的每个父子关系之间,多了一个叫 children 的字段来连接。
举个栗子:
json
// 配置树(骨架+元信息)
{
"page": {
"type": "Page",
"children": {
"logo": {
"type": "Image",
"children": {
"src": { "type": "AssetPicker", "label": "图片地址", "required": true },
"width": { "type": "NumberInput", "label": "宽度", "min": 1 }
}
}
}
}
}
对应的真实值树:
css
{
"page": {
"logo": {
"src": "/my-logo.png",
"width": 200
}
}
}
看到没?结构一模一样,只是真实值去掉了 children 和所有描述性的字段,只剩下纯数据。
路径统一:一个 key 打通两棵树
有了这个同构关系,路径规则就简单到爆:
- 逻辑路径 (我们心里想的节点链):
page.logo.src - 在配置树里找 :把点换成
.children.,得到page.children.logo.children.src,就能一路访问到配置节点。 - 在真实值树里找 :直接用
page.logo.src去取数据。
这就意味着,一个逻辑路径 page.logo.src 既是一个配置节点的唯一标识,又是它在真实值里的位置。双向映射零成本,再也没有那种"配置路径"和"数据路径"对不上的噩梦。
编辑时怎么玩?注入 value 和 pathKey
初始化的时候,我们拿到原始配置,再读一下真实值,给每个叶子节点塞两个字段:
value:当前值(从真实值里取,或默认值)pathKey:就是这个节点的逻辑路径,比如"page.logo.src"
于是运行时配置变成这样:
css
{
"page": {
"children": {
"logo": {
"children": {
"src": {
"type": "AssetPicker",
"label": "图片地址",
"value": "/my-logo.png", // 从真实值拿的
"pathKey": "page.logo.src"
},
"width": {
"type": "NumberInput",
"label": "宽度",
"value": 200,
"pathKey": "page.logo.width"
}
}
}
}
}
}
用户在属性面板上改 width,我们直接改这个节点的 value 为 300,然后因为知道它的 pathKey 是 page.logo.width,就可以立即执行:
ini
realValue.page.logo.width = 300;
画布上的那个组件也就只重新渲染宽度变化的部分,别的纹丝不动。完美。
为什么不能直接用真实值渲染属性面板?
你可能会问:那干脆直接用真实值对象来生成面板呗,干嘛多此一举搞个配置树?
因为真实值里你分不清一个对象到底是"需要递归的子节点",还是"一个普通的样式对象"。比如 style: { width: 100 },它只是个数据容器,不是子组件。但 logo: { src: '...' } 却是一个实实在在的子节点。
配置树用 children 字段清晰地标出了边界:有 children 的才是组件节点,需要递归;没有 children 的对象只是普通属性值。渲染器就靠这个"骨架"来决策什么时候递归,什么时候停。
还有什么好玩的?
- 联动超级简单 :配置节点里可以加
dependencies字段,写上依赖的pathKey,值一变,自己重新算一遍再写回真实值,整个链条自动运转。 - 面板渲染递归化 :属性面板直接根据配置树的
children递归渲染,每个节点根据type选择控件,值绑定到value,只读、必填等约束直接生效。 - 性能炸裂 :每一个
value修改只触发对应pathKey的响应,画布重绘粒度精准到组件属性级,大规模页面也不卡。
总结成一句话
我们用两棵同构的对象树分离了"编辑规则"与"数据内容",通过在配置树节点间插入
children来标记层级,再用统一逻辑路径映射到真实值树,实现了属性级别的精准编辑和渲染。
简单、干净,没有任何多余转换,就是纯粹的"两个长得一样的对象,一个带 children 一个不带,路径互推"。