配置与数据分离:一种可视化搭建的属性编辑方案

我们到底在折腾什么?

可视化搭建工具里,最烦人的就是深层属性的修改。比如改一个图片的边框宽度,路径可能是 style.border.width,老办法是把整个 style 对象读出来,改完再整个塞回去。一不小心就把别的属性弄丢了,性能也烂,整个组件树跟着重绘。

后来我们想,能不能做到:改哪个叶子,就只动那一个叶子,跟它隔壁的属性井水不犯河水。而且,怎么让配置(这个属性怎么编辑)和真实数据(用户填的值)彻底分家,各自清爽。

于是就有了下面这套思路。


核心想法:两棵长得一样的树

整个系统里其实有两棵对象树,结构完全一致:

  1. 配置树(Config Tree) :负责告诉编辑器"这个属性长什么样"------是输入框、下拉、颜色选择器?是不是只读?有没有最大值限制?联动规则是啥?
  2. 真实值树(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 既是一个配置节点的唯一标识,又是它在真实值里的位置。双向映射零成本,再也没有那种"配置路径"和"数据路径"对不上的噩梦。


编辑时怎么玩?注入 valuepathKey

初始化的时候,我们拿到原始配置,再读一下真实值,给每个叶子节点塞两个字段:

  • 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,我们直接改这个节点的 value300,然后因为知道它的 pathKeypage.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 一个不带,路径互推"。

相关推荐
林希_Rachel_傻希希1 小时前
web性能之相关路径——AI总结
前端·javascript·面试
不好听6131 小时前
从零搭建一个 RAG 语义搜索系统 —— DEMO的初始阶段
javascript·面试·llm
何时梦醒1 小时前
上下文工程(Context Engineering):AI 应用开发的新范式 —— 从理论到实战全解析
javascript
竹林8181 小时前
用 wagmi v2 踩坑两天,我终于搞懂了多链钱包切换在 DeFi 前端中的正确姿势
前端·javascript
用户2136610035721 小时前
Vue项目搜索功能与面包屑导航
前端·javascript
星栈1 小时前
LiveView 的实时通信,爽是爽,但 PubSub 和广播也最容易把自己绕晕
前端·前端框架·elixir
用户2930750976691 小时前
告别关键词匹配,拥抱向量语义 —— RAG 搜索从零到一
前端
独孤留白2 小时前
从C到Rust:告别 C 的"指针 + 长度"手动模式
前端·rust
阿黎梨梨2 小时前
揭秘大语言模型的底层逻辑:从文本分词到高维向量的计算之旅
javascript·人工智能