React-dnd 实现无限子集的拖拽构建功能(多层嵌套)


核心是使用递归的方式来索引数据, 如果数据量大, 建议使用自维护的索引方式

最近开发的指纹浏览器NestBrowser 有个拖拽方式建立自动化流程的功能, 使用了react-dnd 这个库, 记录一下 react-dnd的多层嵌套使用

核心是使用递归的方式来索引数据, 如果数据量大, 建议使用自维护的索引方式

代码量较大, 本blog主讲思路和核心代码,其他结合各自业务开发

  • 技术栈 electron, nodejs, react, typescript
  • npm库 react-dnd
  • Ui 组件 material-ui

Config: 最终需要形成的数据

拖拽件(drag)生成唯一uuid来标识,进行排序定位等处理

递归算法:
1/** 2 * 3 * @param configs 配置数组 4 * @param id 拖动的drag id 5 * @param toId 拖到的drop id 6 * @param isOverMid 在拖到drop的上或下插入 7 * @param toChild 是否是拖到子组件里面 8 * @returns 返回新的配置数组 9 */ 10export const moveConfig = ( 11 configs: AutomationConfigProps[], 12 id: string, 13 toId: string, 14 isOverMid = true, // 前后 15 toChild = false, 16): AutomationConfigProps[] => void; 17 18export const addConfigToId = ( 19 configs: AutomationConfigProps[], 20 config: AutomationConfigProps, 21 toId: string, 22 isOverMid = true, 23 toChild = false, 24): boolean => void; 25 26export const deleteConfigById = ( 27 configs: AutomationConfigProps[], 28 id: string, 29): boolean => void; 30 31export const getConfigById = ( 32 configs: AutomationConfigProps[], 33 id: string, 34): AutomationConfigProps => void; 35 36export const editConfigById = ( 37 configs: AutomationConfigProps[], 38 id: string, 39 config: AutomationConfigProps, 40): boolean => void; 41
useGrag 使用
1const [{ isDragging }, drag] = useDrag( 2 () => ({ 3 type: AutomationItemTypes.SORT_BOX, // 常量, 结合Drop使用,声明drag可放置到的区域 4 item: dragCard, // 数据格式, Drop 能接收到的参数 5 end: (item, monitor) => { // 完成放置 6 ...... 7 // source 为每个drop响应父标识,用于处理新增件到哪个父类 8 ...... 9 const [...newConfig] = config; 10 // 新增组件 11 if (source === EMPTY_DROP_BASE) { 12 newConfig.push(addData); 13 setConfig(newConfig); 14 } 15 ...... 16 // 排序新增 17 const res = addConfigToId( 18 newConfig, 19 addData, 20 sortBoxDrop.dragToId, 21 sortBoxDrop.isOverMid, 22 ); 23 if (res === true) setConfig(newConfig); 24 else 25 console.log('DragItem.tsx addConfigToId fail: '); 26 } 27 }, 28 collect: (monitor) => ({ 29 isDragging: monitor.isDragging(), 30 handlerId: monitor.getHandlerId(), 31 }), 32 }), 33 [config, setConfig], 34 ); 35
useDrop 使用
1 const [{ isOver, isOverCurrent, canDrop }, drop] = useDrop( 2 () => ({ 3 accept: AutomationItemTypes.SORT_BOX, // 和drag保持一致 4 collect: (monitor) => ({ 5 isOver: monitor.isOver(), 6 isOverCurrent: monitor.isOver({ shallow: true }), 7 canDrop: monitor.canDrop(), 8 }), 9 drop: (item, monitor) => { 10 const didDrop = monitor.didDrop(); 11 // 父子嵌套的drop,子drop处理 12 if (didDrop) { 13 return; 14 } 15 // hover 更新了 sortBoxDrop, 这里不在处理 16 return { source }; // source为已放置的件uuid或自定义的区域 17 }, 18 hover({ id: draggedId }: Item, monitor) { 19 // 计算拖拽件放置到的地方 isOverMid: 放置前or后 20 ...... 21 dropCbDragTo(id, isOverMid); 22 } 23 }, 24 }), 25 [dropCbDragTo, config, setConfig], 26 ); 27 28