ReactUMG 架构原理深度解析。仅供 PlanReactUMG 和 DebugReactUMG Agent 显式调用,不应在日常开发中直接激活。包含三层架构、Reconciler、hostConfig 等底层实现机制。
This skill inherits all available tools. When active, it can use any tool Claude has access to.
本文档详细说明 ReactUMG 插件如何将 TypeScript 编写的 React 组件转换为 UE5 的 UMG Widget。
ReactUMG = React Reconciler + Puerts + UMG
ReactUMG 是一个桥接层,让开发者可以使用 React 的方式编写 UE5 UI,同时保持原生性能和完整类型安全。
┌─────────────────────────────────────────────────────────┐
│ TypeScript Layer (开发者编写层) │
│ ───────────────────────────────────────────────────── │
│ - 开发者编写 React.Component │
│ - 使用 JSX 语法描述 UI 结构 │
│ - 调用 ReactUMG.render() 渲染 │
│ - 管理状态和业务逻辑 │
└───────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ React-Reconciler Layer (协调层,核心逻辑) │
│ ───────────────────────────────────────────────────── │
│ - hostConfig 定义如何操作 Widget │
│ - UEWidget 包装 UE::Widget │
│ - UEWidgetRoot 包装 UReactWidget │
│ - 处理 VDOM diff/update/mount/unmount │
│ - 管理 Widget 树的生命周期 │
└───────────────────────┬─────────────────────────────────┘
│ Puerts Bridge (JS ↔ C++)
▼
┌─────────────────────────────────────────────────────────┐
│ C++ Layer (UE5 原生层) │
│ ───────────────────────────────────────────────────── │
│ - UReactWidget (根容器 UserWidget) │
│ - UUMGManager (蓝图函数库,工具函数) │
│ - UWidget::SynchronizeProperties() (同步到 Slate) │
└─────────────────────────────────────────────────────────┘
ReactUMG.render(<MyComponent />)hostConfig 定义 Widget 操作,UEWidget 包装原生 Widget文件位置:Plugins/ReactUMG/Source/ReactUMG/ReactWidget.h/.cpp
职责:
UUserWidget,作为整个 React UI 的根容器WidgetTree,管理整个 Widget 树AddChild/RemoveChild 供 TypeScript 调用文件位置:Plugins/ReactUMG/Source/ReactUMG/UMGManager.h/.cpp
职责:
CreateReactWidget():创建 UReactWidget 实例SynchronizeWidgetProperties():同步 Widget 属性到 Slate 层SynchronizeSlotProperties():同步 Slot 属性到 Slate 层为什么需要 Synchronize?
UE5 的 UMG 是双层架构:
当通过 TypeScript 修改 UWidget 的属性时:
puerts.merge 直接修改 C++ 对象的内存SynchronizeProperties() 将改动同步到 Slate 层文件位置:Plugins/ReactUMG/TypeScript/react-umg/react-umg.ts:35-159
职责:
文件位置:Plugins/ReactUMG/TypeScript/react-umg/react-umg.ts:161-192
职责:
UReactWidget,提供 TypeScript 访问接口文件位置:Plugins/ReactUMG/TypeScript/react-umg/react-umg.ts:195-283
职责:
1. 开发者调用:
ReactUMG.render(<TaleSelectPanel />)
↓
2. React Reconciler 递归处理 VDOM:
hostConfig.createInstance("CanvasPanel", props)
→ new UEWidget("CanvasPanel", props)
→ new UE.CanvasPanel()
↓
3. 构建 Widget 树结构:
hostConfig.appendInitialChild(parent, child)
→ parent.appendChild(child)
→ parent.nativePtr.AddChild(child.nativePtr)
↓
4. 应用 Widget 属性:
puerts.merge(widget.nativePtr, {Text: "Hello"})
→ 直接修改 C++ 对象属性
↓
5. 同步属性到 Slate 层:
UE.UMGManager.SynchronizeWidgetProperties(...)
→ widget->SynchronizeProperties()
→ Slate 层更新
↓
6. 显示到屏幕:
hostConfig.resetAfterCommit(root)
→ root.addToViewport(0)
1. 状态变化触发:
this.setState({count: 2})
↓
2. Reconciler Diff Props:
hostConfig.prepareUpdate(...)
→ !deepEquals(...) → return true
↓
3. 提交更新:
hostConfig.commitUpdate(instance, ...)
→ instance.update(oldProps, newProps)
→ puerts.merge(nativePtr, {Text: "2"})
→ UE.UMGManager.SynchronizeWidgetProperties(...)
↓
4. 屏幕更新:
Slate 层重新渲染
1. 初始化时绑定事件:
<Button OnClicked={this.handleClick} />
→ UEWidget.bind("OnClicked", this.handleClick)
→ nativePtr.OnClicked.Add(this.handleClick)
↓
2. 用户交互触发事件:
用户点击按钮
→ UButton::OnClicked.Broadcast()
↓
3. Puerts 桥接转发:
C++ Delegate.Broadcast()
→ Puerts 拦截并转发到 JS
→ this.handleClick() 在 TypeScript 中执行
↓
4. TypeScript 处理事件:
handleClick() { this.setState({clicked: true}) }
→ 触发 Re-render
阶段 0:触发源(用户代码)
├── setState() - 状态更新
├── forceUpdate() - 强制更新
├── Context 变化 - Provider value
└── ReactUMG.render() - 初次挂载
⚠️ 私有成员变量改变不触发任何更新
↓
阶段 1:父组件视角(发起者)
父组件 render()
↓
生成新的 Virtual DOM: <Child propA={newValue} />
↓
Props 流向子组件 (oldProps → newProps)
📌 "Props Change" 是父组件 render 的结果,不是原因
↓
阶段 2:子组件视角(响应者)
Props Changed
↓
shouldComponentUpdate?
├── false → 停止更新
└── true → 子组件 render() → Diff 算法
📌 这就是 "Props Change → SCU → Render"
↓
阶段 3:Commit 阶段(hostConfig)
key/type 变了?
├── 是 → 销毁重建 (removeChild + createInstance + appendChild)
└── 否 → ref 变了?
├── 是 → ref 重绑定 (getPublicInstance)
└── 否 → props 变了?(deepEquals)
├── 是 → 属性更新
│ prepareUpdate() → commitUpdate()
│ puerts.merge(nativePtr, props)
│ SynchronizeWidgetProperties()
└── 否 → 无操作
关键结论:
• React 组件的 Render(生成 VDOM)≠ UE Widget 的 Update(实际修改属性)
• Props Change 发生在两个 Render 之间:父组件 Render 后、子组件 Render 前
• 私有成员变量的改变永远不会触发任何更新
优势:
为什么需要同步?
puerts.merge 修改 UWidget 属性SynchronizeProperties() 触发同步关键点:
init() 时暂存,不立即应用AddChild() 后设置 nativeSlot,触发 setter避免内存泄漏:
使用场景:
统计:
无序列化/反序列化:
puerts.merge 设置多个属性,减少跨界调用类型定义文件:Typing/react-umg/index.d.ts(2406 行)
覆盖范围:
支持所有 React 特性:
useState, useEffect, useContext, useRef 等<></>{condition && <Widget />}{items.map(item => <Widget key={item.id} />)}bind() 时存储清理函数unbindAll() 在 Widget 销毁时自动调用C++ 层:
Plugins/ReactUMG/Source/ReactUMG/ReactWidget.h/.cppPlugins/ReactUMG/Source/ReactUMG/UMGManager.h/.cppTypeScript 层:
Plugins/ReactUMG/TypeScript/react-umg/react-umg.tsTyping/react-umg/index.d.ts(2406 行)- React-UMG 组件类型Typing/ue/index.d.ts - UE5 类型映射TypeScript/src/debug-panel/DebugPanel.tsx - 调试面板(复杂布局)TypeScript/src/tale-select-panel/index.tsx - 剧本选择(居中布局)TypeScript/src/formation-panel/index.tsx - 编队面板(底部布局)TypeScript/src/game-operation-window/ - 运营界面(完整示例)// ⚠️ UE 开发者常见误解
render() {
return (
<CanvasPanel> // ← 不是每次都创建新的!
<DragPreview /> // ← React 会检查 key 决定复用还是重建
</CanvasPanel>
);
}
// React 的实际行为:
// 第一次 render → 创建 CanvasPanel、创建 DragPreview
// 第二次 render → CanvasPanel 无变化则复用,DragPreview 检查 key
// - key 相同 → 复用,只更新 props
// - key 不同 → 销毁旧的,创建新的
| 概念 | UE (C++) | React (TypeScript) |
|---|---|---|
| 创建组件 | CreateWidget<T>() 真的创建 | <Component /> 只是描述 |
| 更新位置 | Widget->SetPosition() | <Component Slot={{...}} /> |
| 组件复用 | 手动管理,同一个指针 | React 自动,key 相同则复用 |
| 强制重建 | RemoveChild + CreateWidget | 改变 key |
| 每帧更新 | NativeTick() | setState() → render() |
ReactUMG 通过极简的 C++ 层和完整的 TypeScript 协调层,成功实现了 React 到 UMG 的无缝桥接:
这种架构让开发者可以用 React 的方式编写 UE5 UI,同时保持原生性能和完整类型安全!
Version: v1.0 Last Updated: 2025-12-23