从flutter看现代UI框架的multi-tree架构设计

0 阅读2分钟

结论先行: 所有现代 UI 框架本质上都是"多棵树"的架构,只是命名和实现细节不同。这是 UI 渲染的通用范式


各框架的"树"对比

框架树1(声明层)树2(实例层)树3(渲染层)
FlutterWidget(不可变配置)Element(可变实例,生命周期)RenderObject(布局绘制)
ReactVirtual DOM(JSX 对象)Fiber(可中断的实例)真实 DOM(浏览器渲染)
VueVNode(虚拟节点)组件实例(响应式系统)真实 DOM
iOS (UIKit)UIView(同时是声明+实例)CALayer(Core Animation 渲染)
Android (View)View(同时是声明+实例)Canvas/HardwareLayer
鸿蒙 ArkUIComponent(ArkTS 声明)Element(实例)RenderNode(自绘)
LynxElement(JS 描述)Layout Object(C++ 实例)Paint Node(自绘)

为什么都需要"多棵树"?

核心矛盾:声明式 UI 的"不可变" vs 渲染系统的"可变状态"

用户写的代码          框架内部              最终输出
───────────         ─────────            ─────────
Widget (不可变)  →  Element (可变)  →  RenderObject (GPU 绘制)
     ↑                  ↑                    ↑
  每次 setState     diff 复用/重建       布局 → 绘制 → 合成
  都创建新的         决定哪些要更新        生成像素
分层职责为什么分离
声明层(Widget/VNode)描述"UI 长什么样"不可变,可随意重建,无性能负担
实例层(Element/Fiber)管理生命周期、状态、父子关系可变,复用避免重复创建
渲染层(RenderObject/DOM)计算布局、生成绘制指令最重量级,变动最少

Flutter 三棵树的详细流转

setState() 触发
    ↓
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Widget    │ ──→ │   Element   │ ──→ │ RenderObject│
│  (新配置)    │     │  (diff 对比) │     │ (布局/绘制)  │
│             │     │             │     │             │
│  Text('A')  │     │  复用或重建  │     │  标记 dirty  │
│     ↓       │     │     ↓       │     │     ↓       │
│  Text('B')  │     │  update()   │     │  layout()   │
└─────────────┘     └─────────────┘     └─────────────┘
        ↑                                    ↓
   开发者只操作这层                      最终 GPU 渲染

关键设计:为什么 Widget 要不可变?

// Widget 是轻量的配置对象
const Text('Hello')  // 创建成本极低,可随意重建

// Element 是重实例
// 复用 Element,只更新 RenderObject

React 的 Fiber 也是同样思想:Virtual DOM 轻量可重建,Fiber 节点复用,真实 DOM 最小变动。


一句话

"多棵树"是声明式 UI 的通用架构模式:声明层负责"写什么",实例层负责"怎么变",渲染层负责"怎么画"。Flutter 的 Widget/Element/RenderObject、React 的 VDOM/Fiber/DOM、Vue 的 VNode/Component/DOM,都是同一套思想的不同实现。