第二章 编辑器技术选型:前端框架对比与拖拽库选择
好,咱们进入正题。这一章聊的是技术选型,说白了就是「用什么造轮子」。我见过不少团队,一上来就开干,结果做到一半发现框架不合适,拖拽库性能拉胯,状态管理乱成一锅粥……嗯,这种坑我踩过不止一次。
所以这一章,我会把前端框架、拖拽库、状态管理这三块掰开揉碎讲清楚。你想想看,选对了工具,后面开发效率能翻倍;选错了,光重构就能让你怀疑人生。
2.1 前端框架对比:React vs Vue vs Angular
先聊框架。低代码平台的核心是「组件化」和「响应式」,这两点决定了我们选型的方向。
| 维度 | React | Vue | Angular |
|---|---|---|---|
| 学习曲线 | 中等(JSX + Hooks) | 低(模板语法直观) | 高(TypeScript + 依赖注入) |
| 组件化 | 函数组件 + Hooks,灵活 | 单文件组件,清晰 | 模块化 + 装饰器,严谨 |
| 响应式 | 手动触发(setState) | 自动追踪(Proxy) | Zone.js 自动检测 |
| 生态 | 最丰富,拖拽库多 | 丰富,但拖拽库偏少 | 官方全家桶,但拖拽库少 |
| 性能 | 虚拟 DOM + Fiber,优秀 | 虚拟 DOM + 编译优化,优秀 | 真实 DOM + 变更检测,中等 |
| 社区活跃度 | 最高 | 高(国内尤其) | 中等 |
我个人习惯用 React。为什么?因为低代码编辑器最核心的交互就是「拖拽」和「状态同步」,React 的 Hooks 机制让这两个场景写起来特别顺手。举个例子,你拖一个按钮到画布上,需要实时更新组件树和属性面板——用 React 的 useState + useCallback 就能优雅搞定。
Vue 也不错,尤其是它的响应式系统。我在项目中遇到过用 Vue 做编辑器的团队,他们利用 reactive 和 computed 自动追踪组件状态变化,写起来确实省心。但问题是,Vue 的拖拽库生态不如 React 丰富,很多高级功能得自己封装。
至于 Angular……嗯,说实话,我不推荐。它的强类型和依赖注入适合大型企业应用,但低代码编辑器需要频繁操作 DOM 和实时反馈,Angular 的变更检测机制反而成了负担。我曾经试过用 Angular 做原型,结果一个拖拽操作触发了一整棵组件树的检测,卡得不行。
我的建议:如果你团队 React 经验多,选 React;如果更看重上手速度,选 Vue。Angular 就跳过吧,除非你们团队全是 Angular 老手。
2.2 拖拽库选择:react-dnd vs dnd-kit
框架定下来,接下来就是拖拽库。这是编辑器的灵魂,选不好后面全是坑。
市面上主流的 React 拖拽库就两个:react-dnd 和 dnd-kit。我两个都用过,说说真实感受。
2.2.1 react-dnd
老牌库,功能强大,但学习曲线陡。它基于 HTML5 拖拽 API,提供了 DragSource 和 DropTarget 两个高阶组件。用起来大概是这样的:
import { useDrag } from 'react-dnd';
function DraggableButton() {
const [{ isDragging }, drag] = useDrag(() => ({
type: 'COMPONENT',
item: { type: 'Button', props: { text: '点击' } },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
}));
return (
<div ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }}>
拖我
</div>
);
}
看着还行对吧?但实际用起来,你会发现几个问题:
- 类型定义繁琐:每个拖拽项都要定义 type,还要手动管理 collect 函数。
- 性能问题:拖拽过程中,整个组件树都会重新渲染,得自己加
React.memo优化。 - 触摸设备支持差:默认不支持移动端,得额外引入
react-dnd-touch-backend。
我在第一个编辑器项目里用的就是 react-dnd,结果被坑惨了。拖拽时页面卡顿,还得手动处理滚动容器和嵌套放置区的冲突。嗯,后来我果断换了。
2.2.2 dnd-kit
dnd-kit 是后起之秀,设计理念更现代。它用 useDraggable 和 useDroppable 两个 Hook,API 简洁很多:
import { useDraggable } from '@dnd-kit/core';
function DraggableButton() {
const { attributes, listeners, setNodeRef, transform } = useDraggable({
id: 'button-1',
data: { type: 'Button', props: { text: '点击' } },
});
const style = transform ? {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
} : undefined;
return (
<button ref={setNodeRef} style={style} {...listeners} {...attributes}>
拖我
</button>
);
}
你看,代码量少了一半。而且 dnd-kit 有几个杀手级特性:
- 自动性能优化:默认只更新拖拽中的元素,不会触发全量渲染。
- 内置碰撞检测:支持矩形、圆形、指针位置等多种碰撞算法。
- 无障碍支持:默认支持键盘拖拽和屏幕阅读器。
- 移动端友好:触摸事件开箱即用。
小技巧:dnd-kit 的 closestCenter 碰撞算法特别适合低代码编辑器的「插入到两个组件之间」场景。我后来做编辑器时,就用它实现了类似 Figma 的自动吸附效果。
所以我的结论很明确:新项目直接上 dnd-kit。除非你维护的是老项目,否则别碰 react-dnd。
2.3 状态管理方案
拖拽搞定了,状态管理又是个大头。低代码编辑器的状态有多复杂?你想想看:
- 组件树(嵌套结构)
- 每个组件的属性(可能几十个字段)
- 选中状态、悬停状态、拖拽状态
- 历史记录(撤销/重做)
- 页面配置(主题、尺寸、断点)
这么多状态,如果全用 useState 硬扛,代码会变成一坨屎山。我见过一个项目,光状态变量就定义了 30 多个,改一个地方得同步五六个 setState……
常见的方案有三种:
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| React Context + useReducer | 中小型编辑器 | 无额外依赖,简单 | 性能差(Context 更新会触发所有消费者) |
| Zustand | 中大型编辑器 | 轻量、API 简洁、性能好 | 生态不如 Redux |
| Redux Toolkit | 大型团队协作 | 规范、中间件丰富、DevTools 强大 | 样板代码多、学习成本高 |
我个人推荐 Zustand。为什么?因为它够轻、够快、够简单。你看它的写法:
import { create } from 'zustand';
const useEditorStore = create((set) => ({
components: [],
selectedId: null,
history: [],
addComponent: (component) =>
set((state) => ({ components: [...state.components, component] })),
selectComponent: (id) => set({ selectedId: id }),
undo: () => set((state) => {
const prev = state.history.pop();
return prev ? { components: prev } : state;
}),
}));
你看,一个 store 就搞定了组件树、选中状态和历史记录。而且 Zustand 默认做了浅比较,不会像 Context 那样一改全刷。
注意:如果你团队有严格的 TypeScript 规范,或者需要时间旅行调试,那 Redux Toolkit 更合适。但说实话,低代码编辑器这种场景,Zustand 的灵活性和性能优势更明显。
我曾经在一个项目里用 Redux Toolkit 做状态管理,结果光定义 slice 和 action 就写了 200 多行。后来换成 Zustand,代码量直接砍半,而且拖拽时的性能提升肉眼可见。
2.4 我的最终选型建议
好了,总结一下我的推荐组合:
- 前端框架:React 18+(Hooks 模式)
- 拖拽库:dnd-kit(最新版)
- 状态管理:Zustand(配合 immer 处理不可变数据)
这个组合我用了两年,做了三个编辑器项目,没出过大问题。当然,如果你团队 Vue 更强,也可以换成 Vue 3 + vue-draggable-next + Pinia,思路是一样的。
下一章,我们会开始搭建编辑器的整体架构。到时候你会看到,选对了工具,架构设计会变得特别顺畅。