低代码 Runtime 的调度系统:“数据变化 → UI更新”的最小架构

18 阅读3分钟

一、背景

在我的低代码 Runtime 设计中,一个核心问题是:当数据发生变化(ctx.set(A,newValue))时,如何驱动 UI 更新?
本质问题是在解决:数据变化如何传播出去,并最终驱动 UI 更新


二、问题抽象

从架构角度看,这其实是一个:变化传播模型
常见的思路有:

  • 依赖驱动(如 Vue)
  • 调度驱动(本文方案)
    本文关注的是:中心化调度模型

三、核心设计

1. 事件驱动

它解决的是:变化 → 事件
数据变化不直接操作 UI,而是转化为事件:

ctx.set('state.count', 4)
   ↓
notify()
   ↓
notifyUpward() 

本质:变化先被“广播”出去


2. 中心化调度

它解决的是:事件汇聚
所有变化最终汇聚到统一入口:rootContext(运行态的根数据节点上下文,调度中心,下文简写rootCtx)

  • 所有节点的 ctx 的更新都会向上冒泡
  • rootCtx 作为统一调度节点

本质:所有更新都有唯一入口


3. 渲染触发:统一执行

RendererRoot 订阅 rootCtx
当 rootCtx 收到变化:
→ 触发 Renderer 层
→ UI 更新


四、完整执行链路

ctx.set
   ↓
事件传播(notify / 冒泡)
   ↓
rootCtx(统一汇聚)
   ↓
RendererRoot(订阅者)
   ↓
Renderer 层
   ↓
UI 更新

示例代码:

// 订阅者
type Listener = () => void

// 运行态的能力上下文
class RuntimeContext {
  parent?: RuntimeContext
  private listeners = new Set<Listener>()
  state: Record<string, any> = {}

  subscribe(fn: Listener) {
    this.listeners.add(fn)
    return () => this.listeners.delete(fn)
  }

  notify() {
    this.listeners.forEach(fn => fn())
  }

  // 向上冒泡,直到 rootCtx
  notifyUpward() {
    this.notify()
    this.parent?.notifyUpward()
  }

  set(path: string, value: any) {
    this.state[path] = value
    this.notifyUpward()
  }
}

// 运行态控制中心
class Runtime {
  private rootCtx = new RuntimeContext()

  getRootContext() {
    return this.rootCtx
  }
}

// 渲染根节点(渲染器部分)
function RendererRoot({ runtime }: { runtime: Runtime }) {
  const [tick, setTick] = useState(0)

  useEffect(() => {
    const rootCtx = runtime.getRootContext()

    return rootCtx.subscribe(() => {
      setTick(t => t + 1) // 触发整树 rerender
    })
  }, [runtime])

  return <Renderer />
}

// 某个运行时的交互逻辑中
const rootCtx = runtime.getRootContext()
rootCtx.set('count', 2)

五、设计本质

这套方案可以抽象为四层:

  • 数据层:负责“发生变化”
  • 事件层:负责“传播变化”
  • 调度层:负责“组织更新”
  • 视图层:负责“消费结果”

一句话总结:由运行时统一接管 变化 驱动 UI 的过程


六、与 Vue 响应式的区别

维度本方案Vue
模型中心化调度依赖驱动
更新方式全局调度精确触发
依赖关系不显式记录自动收集
性能特点简单场景够用,复杂场景需优化精确更新,性能好
调试难度容易(单一入口)中(依赖追踪链)

本质区别:

  • Vue:数据决定谁更新
  • 本方案:系统决定什么时候更新

七、适用场景

1. 低代码

Schema 驱动 UI,模块复杂,需要统一调度

2. 表单引擎

字段联动:比如下拉选项 改变 → 控制 输入框 显示/隐藏

3. 可视化编辑器

拖拽 / 选中 / 属性修改 → 触发整体更新

4. 插件系统 / IDE

多个模块同时影响 UI,需要统一控制更新节奏


八、总结

本方案是一种“调度驱动”的响应式模型,通过中心化调度统一控制 UI 更新。
它的价值不在于“更快”,而在于:更可控、更易扩展、更适合复杂系统


九、思考

在实际架构中,最优解往往是:中心化调度 + 依赖驱动(混合模型),这也是前端响应式系统的演进方向。
所以我这套方案的演进路径:

  • v1:整树刷新(当前最小可行)
  • v2:依赖追踪 & 局部更新(建立 data → UI 关系,实现局部更新)
  • v3:调度优化(批处理 / 优先级)

核心方向:从“调度驱动” → “调度 + 依赖驱动”