拖拽吧,前端侠!低代码平台的快乐‘搬砖’日常

207 阅读4分钟

引言

你是否曾梦想过像搭积木一样开发应用?低代码平台拖拽组件,让前端开发者也能体验“所见即所得”的快乐!(◕‿◕)


实现原理

核心思路:物料区组件通过拖拽,动态植入画布区的 JSON 组件树,实现可视化页面搭建。

  • 组件仓库(componentsStore)负责管理整个页面的组件树,支持增删改查。
  • 组件配置仓库(componentConfigStore)记录每个组件的元数据与渲染逻辑。
  • 递归渲染:renderComponents 函数利用 React.createElement 递归生成真实 DOM。
  • 拖拽交互:基于 react-dnd,物料区组件可拖拽到画布区,自动生成对应 JSON 节点。
  • useDrop 封装为自定义 hook,提升复用性与可维护性。

一、项目准备与核心依赖

平台采用 React 19 + TypeScript + Vite 构建,核心依赖包括:

  • Ant Design 5:UI 组件库,提升界面一致性与开发效率。
  • TailwindCSS:原子化 CSS,极大简化样式管理。
  • Zustand:轻量级状态管理,驱动组件树和配置。
  • Allotment:实现编辑器区域的可拖拽分栏布局。
  • react-dnd:实现物料区到画布区的拖拽交互。
  • Monaco Editor:源码编辑与展示。

目录结构清晰,src/editor 下包含核心组件、hooks、物料、仓库等。

关于该项目的完整代码,你可以在这个仓库中查看:内附项目体验地址
「仓库链接」github.com/LZY-Ricardo…


二、组件封装与仓库设计

1. 组件封装:以 Container 为例

import { useMaterialDrop } from '../../hooks/useMaterialDrop'
export default function Container({ id, children, styles }) {
  const { canDrop, dropRef, contextHolder } = useMaterialDrop(['Button', 'Container'], id)
  return (
    <>
      {contextHolder}
      <div data-component-id={id} ref={dropRef as any} className={`min-h-[100px] p-[20px] ${canDrop ? 'border-[2px] border-[blue]' : 'border-[1px] border-[#000]'}`} style={styles}>
        {children}
      </div>
    </>
  )
}

设计分析:

  • 组件通过 useMaterialDrop 封装拖拽能力,支持接收其他组件拖入。
  • canDrop 动态改变边框样式,提升交互体验。
  • 结构简洁,便于扩展和复用。

2. 组件配置仓库(componentConfigStore)

export const useComponentConfigStore = create<State & Action>(
  (set) => ({
    componentConfig: {
      Container: { name: 'Container', ... },
      Button: { name: 'Button', ... },
      // ...
    }
  })
)

设计分析:

  • 统一管理所有可用组件的元数据(如默认属性、描述、开发/生产组件等)。
  • 支持动态注册新组件,便于平台扩展。

3. 组件树仓库(componentsStore)

export const useComponentsStore = create<State & Action>(
  (set, get) => ({
    components: [ { id: 1, name: 'Page', ... } ],
    addComponent: (component, parentId) => { ... },
    deleteComponent: (componentId) => { ... },
    updateComponentProps: (componentId, props) => { ... },
    // ...
  })
)

设计分析:

  • 用于维护画布上的组件树(JSON),所有操作(增删改)都围绕这个仓库展开。
  • 通过 addComponentdeleteComponent 等方法,保证组件树的响应式和一致性。

三、拖拽组件的实现与原理

1. 拖拽能力的抽象与复用

export function useMaterialDrop(accept: string[], id: number) {
  const { addComponent } = useComponentsStore()
  const { componentConfig } = useComponentConfigStore()
  const [messageApi, contextHolder] = message.useMessage();
  const [{ canDrop }, dropRef] = useDrop(() => ({
    accept,
    drop: (item, monitor) => {
      if (monitor.didDrop()) return
      messageApi.success(item.type)
      const props = componentConfig?.[item.type]?.defaultProps
      const desc = componentConfig?.[item.type]?.desc
      addComponent({ id: Date.now(), name: item.type, props, desc, styles: {} }, id)
    },
    collect: (monitor) => ({ canDrop: monitor.canDrop() })
  }))
  return { canDrop, dropRef, contextHolder }
}

设计分析:

  • useDrop 逻辑抽离为 useMaterialDrop,实现拖拽逻辑的复用和解耦。
  • 拖拽落下时自动将组件对象插入到 JSON 树,实现“所见即所得”。
  • 通过 canDrop 实现拖拽反馈,提升交互体验。

2. 递归渲染组件树

function renderComponents(components) {
  return components.map((component) => {
    const config = componentConfig?.[component.name]
    if (!config.prod) return null
    return React.createElement(
      config.prod,
      { key: component.id, id: component.id, ... },
      renderComponents(component.children || [])
    )
  })
}

设计分析:

  • 利用递归和 React.createElement,将 JSON 组件树动态渲染为真实页面结构。
  • 支持组件嵌套和属性传递,保证画布区的灵活性和可扩展性。

效果图:

低代码拖拽.gif

四、设计思路与效果总结

  • 为什么这样做?
    • 组件封装与仓库分离,保证了平台的高扩展性和可维护性。
    • 拖拽逻辑抽象为 hook,极大提升了代码复用率和可读性。
    • 递归渲染与响应式仓库结合,实现了“所见即所得”的低代码体验。
  • 这样做的效果?
    • 用户可自由拖拽、组合组件,实时预览页面结构。
    • 属性编辑、组件树操作、源码同步一体化,极大提升开发效率和平台易用性。

通过上述设计与实现,低代码平台不仅实现了灵活的组件拖拽与动态渲染,还为后续功能扩展(如自定义物料、复杂交互等)打下坚实基础。

项目所有代码均已整理至以下仓库,方便大家查看与使用:内附项目体验地址
→ 代码仓库地址