半年时间用nextjs开发3d场景编辑器的感受

4 阅读5分钟

复杂状态管理下,Vue 比 React 更顺手

最近我在用 React + Babylon.js 做一个 3D 场景编辑器,踩了不少坑。大部分坑集中在两块:闭包陷阱和状态同步。折腾了一段时候后,我越来越觉得:在这类复杂交互的应用里,Vue 从架构层面就比 React 更适合。

记录一下遇到的几个痛点和排查过程。


背景

我们的 3D 编辑器有这几个特点:

  1. 状态树深: 一个场景里有很多 Mesh,每个 Mesh 有坐标、旋转、缩放、材质等,材质下面还有贴图、颜色等几十个属性。
  2. 复杂的交互事件系统: 交互操作繁多,涉及到材质的克隆替换、模型状态的大量改变,以及根据这些操作生成实时的场景弹窗。
  3. 多源头与高频修改: 坐标可以被 3D 视口里的鼠标拖动修改,也可以被右侧属性面板输入修改。鼠标拖拽坐标轴时,Babylon.js 一秒会触发几十次事件,右侧面板必须跟着实时跳动。
  4. 底层引擎设计: Babylon.js 的 Mesh 是类的实例,改向量直接 mesh.position.x = 10,本身就是可突变(Mutable)的设计。

遇到的痛点

1. 闭包带来的旧状态问题

3D 编辑器大量逻辑需要绑定到引擎事件里,比如在 scene.onPointerDown 里判断当前选中的工具类型,或者触发刚才提到的场景弹窗。

如果在 React 的 useEffect 里绑定这些事件,并在回调内部读 useState 变量,拿到的基本是绑定时的旧值。这是 JS 闭包基本行为,由于 React 组件每次渲染都是新快照,引擎持有的回调没更新就导致了这个问题。

解决办法是用 useRef 镜像一份最新状态,或者用 useStore.getState() 手动拉取。但这会导致代码里有响应式和命令式两套获取数据的方式,很难看。

2. 全局右键菜单带来的心智负担

项目里有一个复杂的点是全局的右键菜单方案。右键菜单可以被多处触发,比如在 3D 视口里右键模型,或者在其他面板里右键操作。

这涉及到保存当前的 select 状态和执行很多上下文相关的指令。在 React 里,因为多处触发和闭包的问题,要保证右键点击时执行的回调函数里拿到的是最新上下文(比如“刚才选中了什么”),需要极为小心地管理依赖项。一层层传递传递,稍不留神依赖写漏了,右键操作就错乱了。心智负担极重。

3. 性能防御带来的代码负担

React 是不可变数据,更新深层属性必须克隆整条对象链。

麻烦在于,顶层状态一变,连着这个 Store 的组件默认全跑一遍。哪怕只改 X 坐标,无关的材质面板、相机设置也会重新 render。60fps 频率下,页面很快就会卡顿。

所以必须写大量防御:React.memo 包组件,useCallback 包回调,useMemo 包计算结果,Zustand 的 Selector 拆得很细。这部分工作不产生业务价值,还容易踩坑。

4. 状态范式和引擎的割裂

Babylon 对象是面向对象的可变实例,不能直接放进 Zustand,因为内部变化不改引用地址,React 感知不到。

必须手动写同步层:引擎状态变化时,提取需要展示的字段转成普通对象给 setState;React 输入修改了,再写回引擎实例。交互涉及到“材质的克隆替换”这种大动作时,同步层很容易写漏字段或时机不对,导致 UI 和场景状态不一致,排查起来很费劲。


如果换成 Vue 会怎样

Vue 3 基于 Proxy,天然兼容可突变对象,基本解决了上面这些问题。

  1. 没有闭包问题: <script setup> 只执行一次。reactive 创建的 Proxy 持久存在,无论传到多深的闭包(比如绑定给 3D 引擎的回调、全局右键菜单触发的函数),state.selected 拿到的永远是内存真实值。右键菜单的保存和多处触发变得非常纯粹,完全不需要在依赖数组里挣扎。
  2. 内置细粒度更新: Vue 是属性级依赖追踪。改了 position.x,只有绑定了这个属性的输入框会更新,其他面板完全不管。不需要手动 memo,默认就能扛住 60fps 高频拖拽,连带着那些复杂的场景弹窗也能精准渲染。
  3. 拥抱可变对象: 生成弹窗、替换克隆材质,直接 sceneState.mesh.material = newMaterial 赋值,Vue 就能拦截并更新 UI。可以直接在引擎事件里写逻辑赋值给响应式状态,不需要同步层,引擎和 UI 共享数据。

总结

如果是普通的管理后台、信息流,用 React 没毛病,不可变数据让状态历史清晰,好写测试。

但如果是 3D 编辑器这种包含复杂交互事件、频繁模型材质替换、到处飘着场景弹窗,并且有很多像“右键多处触发菜单”这样对闭包依赖极其敏感的应用,Vue 的响应式系统确实能省掉很多不必要的心智负担。

以后遇到类似项目,我会直接选 Vue。