从低代码到 AI 搭 UI:Schema 驱动的 DSL 引擎

0 阅读5分钟

从低代码到 AI 搭 UI:Schema 驱动的 DSL 引擎

如果你只想用一句话理解 Vario:它是一个 Schema-First UI 运行时,用 JSON 描述界面和交互,用 Action VM 执行逻辑,再把它渲染成 Vue 3 VNode。

QQ20260307-205352.png

我一开始只是想做一个低代码平台里的动态渲染层:后端下发一份 JSON,前端按配置把页面画出来。最先想到的方案当然是 Vue 递归组件,但很快就撞上了几个硬问题。

第一,性能不行。节点一多,递归组件就会变成大量 Vue 实例,更新粒度卡在组件层,想精准跳过没变化的子树并不容易。第二,事件和双向绑定很别扭。JSON 里写的是字符串和配置,到了递归组件里却要手动转成函数、emit 链和状态同步逻辑,写着写着就像在框架外面再套一层小框架。第三,表达式求值是安全坑。你不可能放心把来自数据库或 AI 的表达式直接丢给 eval。

后来我意识到,问题不在 Vue,而在抽象层级错了。递归组件仍然是在用“组件”的粒度思考,但 Schema 驱动 UI 真正需要的是“节点级控制权”。于是我做了 Vario。

它到底是什么

Vario 不是组件库,也不是整套低代码平台。它更像一个中间层运行时:

  • @variojs/schema 负责定义、验证和规范化 Schema
  • @variojs/core 负责 RuntimeContext、表达式引擎和 Action VM
  • @variojs/vue 负责把 Schema 渲染成 Vue 3 VNode

核心约束只有一个:Core 层零 Vue 依赖。也就是说,状态模型、表达式求值和动作执行都不绑定 Vue,本质上是框架无关的。这也是为什么它将来可以继续往 React 或其他渲染器扩展。

它解决了什么问题

Vario 想解决的不是“怎么再造一个模板语法”,而是“怎么把 UI 结构和行为变成可序列化、可校验、可增量修改的数据”。

这意味着你可以把界面写成下面这种对象:

const { vnode, state, findById, findAll } = useVario({
  type: 'ElForm',
  children: [
    {
      type: 'ElInput',
      model: 'name'
    },
    {
      type: 'ElButton',
      props: { disabled: '{{ !name }}' },
      events: { click: [{ type: 'call', method: 'submit' }] },
      children: '提交'
    }
  ]
}, {
  state: { name: '' },
  methods: {
    submit: ({ state }) => console.log(state.name)
  }
})

const submitButton = findById('submit-btn')
const inputs = findAll((node) => node.model)

如果你熟悉 Vue,会发现这里的心智模型几乎没变:组件名还是组件名,model 对应 v-model,事件修饰符还是点语法,表达式也只是从模板里搬到了 {{ }} 里。但 useVario() 不只是返回渲染结果和状态,它还暴露了 findfindAllfindById 这类查询能力,让 Schema 树在运行时依然是可检索、可分析、可操作的数据。Vario 想做的不是发明一套陌生 DSL,而是尽量沿用 Vue 开发者已经熟悉的语义。

为什么它比直接写 h() 更有意义

这个项目最重要的一点,是把 UI 从“代码”变成“数据”。

如果你直接写 h(),当然也能渲染出同样的界面,而且类型推导更完整,运行时也更直接。但 h() 本质上还是函数调用,难以序列化、难以校验、难以存数据库,也不适合让 AI 稳定地产生和修改。

Schema 则不一样。它可以被 JSON.stringify,可以在运行前做结构验证和表达式白名单校验,也可以只 patch 某个路径,而不是每次重建整棵树。更重要的是,它在运行时仍然保留结构信息,你可以直接查询整棵树,找出某个节点、某类组件,或者所有带 model 绑定的输入项。对低代码、服务端下发、可视化编辑器、AI 生成 UI 这些场景来说,这种差异不是语法偏好,而是系统边界的差异。

Vario 的三个关键设计

第一是 Action VM。交互逻辑不再散落在事件回调里,而是变成 callsetifloopbatch 这些可组合指令。Schema 负责描述“做什么”,methods 负责真正的业务实现,两者分离后,既安全也更适合配置化。

第二是表达式沙箱。Vario 用 Babel AST 做白名单校验,只允许受控表达式执行,明确禁止 evalFunctionwindowprototype 这类危险入口。对 AI 生成或远端下发的 Schema 来说,这不是锦上添花,而是底线。

第三是渲染优化。Vario 做了 Path Memo、循环项组件化、子树组件化等多层优化,目标是让大规模 Schema 更新时尽量只重渲染真正变化的那部分节点,而不是整棵树跟着一起动。

为什么我觉得它适合 AI 时代

因为 AI 生成 JSON,通常比生成一整套 Vue SFC 更稳定。

你让 AI 直接写页面代码,它可能 import 写错、生命周期放错、组件名拼错,最后生成的是“看起来像代码”的东西。可如果目标换成 Schema,AI 只需要输出结构化数据;剩下的安全校验、表达式限制、动作执行、渲染映射,都由运行时接管。

更重要的是,AI 不必每次重写整页。用户说“把列表改成卡片布局”,它完全可以只返回一个 patch,系统按路径增量更新。这种工作流,比“生成整段前端代码再人工修”更像真正可落地的产品形态。

它适合谁

如果你要的是成熟表单生态,Formily 仍然更强;如果你要的是快速搭内部工具,amis 也依然是更省力的选择。Vario 适合的是另一类问题:你想自己掌控渲染层,想让 Schema 成为系统里的稳定中间表示,或者正在做低代码、AI UI 生成、跨框架渲染这类更偏底层的能力。

所以这不是一个“替代所有方案”的项目,而是一个把 UI 描述、交互逻辑和渲染执行拆开的运行时实验。它想回答的问题是:当 UI 不再只是手写组件,而要变成可分析、可传输、可增量修改的数据时,前端运行时应该长什么样。

这就是 Vario。

GitHub:github.com/YuluoY/vari…

在线演示:yuluoy.github.io/vario/

文档:yuluoy.github.io/vario/docs/

更多文章