在前期的快速迭代阶段,虽然界面有些杂乱,但整体功能尚能凑合运行。真正让人头疼的,还是接下来几个关键功能的实现。
遇到的问题
双向绑定
在Vue中,v-model
提供了方便的双向绑定功能,它是modelValue
属性和onUpdate:modelValue
事件的语法糖。然而,我的低代码开发基于JSON配置,每个细节都需要通过代码控制,因此我是通过Vue的h
函数进行渲染,想要实现类似v-model
的双向绑定体验却相当困难。经过多次版本迭代,才勉强实现了基本的双向绑定。
示例如下:
{
"id": "component_1xxx",
"label": "组件",
"uiType": "component",
"uiName": "组件",
"domType": "div",
"children": [
{
"id": "component_2xxx",
"uiType": "component",
"domType": "el-input",
"attrs": [
{
"label": "输入值",
"key": "modelValue",
"type": "string",
"value": "",
"id": "modelValue_1xxx"
}
]
}
],
"data": [
{
"id": "var_1xxx",
"label": "变量_1",
"value": "变量1值"
}
],
"bind": {
"modelValue_1xxx": "var_1xxx",
}
}
这个配置的意思是,将el-input
组件的“输入值”属性与“变量_1”绑定。
为了管理这种绑定关系,我在最上层的bind
字段中维护变量绑定关系。当el-input
的值变化时,我需要通过onUpdate:modelValue
事件,先查找bind
中是否有绑定关系,再修改data
中的变量值。整个过程繁琐,必须不断地向上触发emit
事件,直到最顶层处理。这种设计不仅冗长,层级深时还容易出错,甚至影响性能。
我曾考虑通过inject
的方式,将最外层配置传递到子组件中,减少事件触发的层级,但最终放弃了。
新的解决方案
在新版本中,我提出了一个新的思路:在顶层创建一个当前组件或页面的实例(VNode)上下文(context),在创建VNode时初始化所有变量,并将其传递给子组件,让每个组件自己去处理和修改变量值。
这个思路已经在工作项目中实现了一部分不经常变化的页面动态化,至今为止,用户还没有发现这些页面是通过低代码配置生成的。
示例代码:
// 创建应用实例但不挂载到 DOM 中
const app = createApp(MyComponent);
// 使用一个虚拟的 DOM 元素来挂载
const vnodeContainer = document.createElement("div");
document.body.appendChild(vnodeContainer); // 需要在文档中才能完成挂载
runtimeInstance.value = app.mount(vnodeContainer);
// 移除挂载点从而使组件不显示在页面上
app.unmount();
console.log("instance: ", runtimeInstance); // 可以访问组件实例
这种设计有一个明显的好处,就是将运行时状态与配置(JSON)完全隔离开。运行时的变化只影响运行时的对象,而不会对JSON配置产生任何改动。对低代码开发而言,JSON配置就是“程序”,运行时的变化不应该直接修改配置数据。这样,即便页面崩溃,也能快速恢复到初始状态。
持续的难题
尽管引入了VNode上下文的方式解决了一些问题,但bind
字段的绑定方式依然存在,无法彻底抛弃。这增加了代码的复杂度,涉及到多个方面的逻辑,改造起来依然面临诸多挑战。
体验最新的轻构低代码
账号:test
密码:123456