从 0 到 1 实现低代码编辑器的基本功能

低代码平台近年来成为前端领域的热门话题。它极大地降低了应用开发门槛,让非专业开发者也能通过拖拽、配置等方式快速搭建页面和业务流程。本文将以 lowcode-editor 项目为例,详细讲解如何从零开始实现一个基础的低代码编辑器,涵盖项目结构、核心技术选型、数据结构设计、拖拽实现、递归渲染、属性编辑、状态管理、预览与源码查看等关键环节。

一、项目结构与技术栈

1. 项目结构

项目基于 React + TypeScript,目录结构如下:

kotlin 复制代码
lowcode-editor/
├── src/
│   ├── editor/
│   │   ├── components/      // 编辑器核心组件
│   │   ├── hooks/           // 自定义 hooks
│   │   ├── materials/       // 物料区组件
│   │   ├── stores/          // 状态管理
│   │   ├── index.tsx        // 编辑器主入口
│   │   └── interface.ts     // 类型定义
│   ├── App.tsx
│   └── main.tsx
├── public/
├── package.json
└── ...

2. 技术栈

  • React + TypeScript:主流前端框架,类型安全,组件化开发。
  • TailwindCSS:原子化 CSS 框架,用类名来使 css 生效,提升样式开发效率。
  • zustand:轻量级状态管理库,简单易用。
  • react-dnd:实现拖拽交互的核心库。
  • Allotment:实现容器分栏拖动,提升布局灵活性。
  • @monaco-editor/react:集成源码编辑器,方便查看和编辑 JSON 数据。

二、低代码编辑器的核心原理

1. 三大区域划分

  • 物料区:左侧,展示可拖拽的组件(如按钮、容器等)。
  • 画布区:中间,拖拽组件到此区域,实时生成页面结构。
  • 属性区:右侧,选中组件后可编辑其属性。

项目演示:

这种布局便于用户直观操作,快速搭建页面。

2. 用 JSON 维护页面结构

低代码编辑器的核心是用一个 JSON 对象来描述页面结构。每次拖拽、编辑属性,都是在操作这个 JSON。如下是典型的数据结构:

typescript 复制代码
// src/editor/interface.ts
export interface ComponentNode {
  id: string;
  type: string;
  props: Record<string, any>;
  children?: ComponentNode[];
}

页面的所有组件都以树状结构存储在 components 数组中,根节点通常是一个容器型组件。

3. 拖拽实现原理

  • 物料区组件支持拖拽(react-dnd)。
  • 画布区支持放置(useDrop),接收拖拽过来的组件。
  • 拖拽成功后,往 JSON 结构中插入新组件。
typescript 复制代码
// src/editor/hooks/useDropComponent.ts
import { useDrop } from 'react-dnd';

const [{ isOver }, drop] = useDrop({
  accept: 'material',
  drop: (item) => {
    addComponentToJson(item);
  },
  collect: (monitor) => ({
    isOver: monitor.isOver(),
  }),
});

4. 递归渲染 JSON 结构

React.createElement 递归把 JSON 渲染成真实页面。

typescript 复制代码
// src/editor/utils/renderComponents.ts
function renderComponents(node: ComponentNode) {
  const Comp = componentMap[node.type];
  return React.createElement(
    Comp,
    node.props,
    node.children?.map(renderComponents)
  );
}

这种递归渲染方式可以灵活支持任意嵌套的组件结构。

5. 属性编辑与响应

  • 选中画布区的组件,右侧展示属性编辑框。
  • 修改属性时,直接更新 JSON 对应节点的 props。
typescript 复制代码
// src/editor/components/PropsEditor.tsx
function handlePropChange(key: string, value: any) {
  updateComponentProps(selectedId, key, value);
}

属性编辑器通常会根据组件的 setter 配置动态生成表单项,提升通用性。

6. 蒙层与交互体验

  • 鼠标悬浮/点击画布组件时,显示蒙层高亮。
  • 点击可选中,右侧属性区联动。
  • 支持删除组件。
typescript 复制代码
// src/editor/components/HoverMask.tsx
<div className={`absolute border-2 border-blue-400 pointer-events-none`} style={maskStyle} />

蒙层的实现通常需要获取目标组件的几何属性,动态调整蒙层位置和大小。

三、状态管理:为什么需要两个仓库?

src/editor/stores/ 目录下,有两个核心状态仓库:

  • components.tsx:用于管理整个页面的组件树(即页面结构 JSON),负责组件的增删改查、选中、拖拽等操作。
  • component-config.tsx:用于管理所有物料组件的配置(如渲染函数、属性 setter、默认属性等),实现组件名与真实组件代码的映射。

为什么要分成两个仓库?

  1. 关注点分离:组件树仓库只关心页面结构和实例数据,组件配置仓库只关心物料定义和渲染逻辑,互不干扰,便于维护和扩展。
  2. 灵活扩展:新增物料组件时,只需在配置仓库注册即可,不影响页面结构数据。
  3. 高效查找与渲染:渲染页面时可通过组件名快速查找对应的渲染函数和属性配置。
  4. 属性编辑解耦:属性编辑器可根据组件配置仓库的 setter 自动生成表单,无需硬编码。

2. 画布区接收拖拽

画布区通过 useDrop 接收拖拽过来的组件,并将其插入 JSON 结构。

typescript 复制代码
// src/editor/components/EditArea.tsx
const [, drop] = useDrop({
  accept: 'material',
  drop: (item) => addComponent(item),
});
return <div ref={drop}>...</div>;

3. 状态管理(zustand)

使用 zustand 管理组件树和选中状态。

typescript 复制代码
// src/editor/stores/componentsStore.ts
import create from 'zustand';
const useComponentsStore = create((set) => ({
  components: [],
  addComponent: (comp) => set((state) => ({ ...state, components: [...state.components, comp] })),
  // ...
}));

4. 递归渲染

递归渲染 JSON 结构,支持任意嵌套。

typescript 复制代码
// src/editor/utils/renderComponents.ts
function renderComponents(node) {
  // ...见上文
}

5. 属性编辑

属性编辑器根据组件的 setter 配置动态生成表单。

typescript 复制代码
// src/editor/components/PropsEditor.tsx
<input value={value} onChange={e => handlePropChange(key, e.target.value)} />

6. 蒙层高亮

通过绝对定位实现蒙层高亮,提升用户交互体验。

typescript 复制代码
// src/editor/components/HoverMask.tsx
<div style={maskStyle} className="absolute border-2 border-blue-400" />

7. 组件树与源码查看

  • 组件树:用 antd 的 Tree 组件展示 JSON 结构,便于整体把控页面结构。
  • 源码区:用 Monaco Editor 展示 JSON 数据,支持直接编辑。
typescript 复制代码
// src/editor/components/SourceCode.tsx
<MonacoEditor value={JSON.stringify(json, null, 2)} language="json" />

8. 预览功能

预览模式下递归渲染 JSON,触发真实事件。

typescript 复制代码
// src/editor/components/Preview.tsx
return <div>{renderComponents(json)}</div>;

编辑模式与预览模式的主要区别在于事件处理和样式控制。

五、完整开发流程

  1. 初始化项目,配置 React、TypeScript、TailwindCSS。
  2. 设计 JSON 数据结构,定义组件节点类型。
  3. 实现物料区,支持组件拖拽。
  4. 实现画布区,支持接收拖拽并插入组件到 JSON。
  5. 实现递归渲染,将 JSON 渲染为真实页面。
  6. 实现属性区,支持动态编辑组件属性。
  7. 实现组件高亮、选中、删除等交互。
  8. 实现组件树和源码区,便于结构和数据管理。
  9. 实现预览功能,区分编辑与预览模式。
  10. 持续优化交互体验和代码结构。

六、总结

低代码编辑器的核心在于:用 JSON 结构描述页面,拖拽和属性编辑本质上是对 JSON 的增删改查,递归渲染则将 JSON 转化为真实页面。通过合理的数据结构设计、组件配置、状态管理和交互优化,可以实现一个功能完善、易于扩展的低代码平台。两个状态仓库的分离设计,使得页面结构与物料配置解耦,极大提升了系统的灵活性和可维护性。

源码地址:github.com/Code-JHua/L...

相关推荐
前端小咸鱼一条5 分钟前
React中的this绑定
前端·javascript·react.js
影子信息10 分钟前
vue vxe-table :edit-config=“editConfig“ 可以编辑的表格
前端·javascript·vue.js
YGY Webgis糕手之路11 分钟前
Cesium 快速入门(四)相机控制完全指南
前端·经验分享·笔记·vue·web
JavaDog程序狗14 分钟前
【软件环境】Windows安装NVM
前端·node.js
黑土豆17 分钟前
为什么我要搞一个Markdown导入组件?说出来你可能不信...
前端·javascript·markdown
前端小巷子20 分钟前
Vue 2 响应式系统
前端·vue.js·面试
前端小咸鱼一条36 分钟前
React的基本语法和原理
前端·javascript·react.js
qq_2787877737 分钟前
Golang 调试技巧:在 Goland 中查看 Beego 控制器接收的前端字段参数
前端·golang·beego
YGY Webgis糕手之路37 分钟前
Cesium 快速入门(六)实体类型介绍
前端·经验分享·笔记·vue·web
come1123439 分钟前
前端ESLint扩展的用法详解
前端