如何在mxgraph(drawio)图形中嵌入可交互React组件
mxGraph与React集成的可行解决方案
针对预渲染React组件无法触发事件/Hooks、更新效率低下,以及React Portal被覆盖的问题,以下是几个可行的集成思路:
1. 利用mxCellRenderer自定义渲染钩子
mxGraph允许通过重写mxCellRenderer的方法自定义单元格渲染逻辑,直接在顶点DOM中挂载原生React组件,让React完全接管组件的生命周期和事件处理:
// 重写mxCellRenderer的顶点形状创建方法 mxCellRenderer.prototype.createVertexShape = function(state) { // 调用原生方法创建基础形状 const shape = mxCellRenderer.prototype.createVertexShape.apply(this, arguments); // 从单元格数据中获取React组件及props const { component: ReactComponent, props } = state.cell.value || {}; if (!ReactComponent) return shape; // 创建React组件挂载容器 const reactContainer = document.createElement('div'); reactContainer.style.width = '100%'; reactContainer.style.height = '100%'; shape.appendChild(reactContainer); // 挂载React组件 ReactDOM.render(<ReactComponent {...props} />, reactContainer); // 监听形状销毁事件,卸载React组件防止内存泄漏 shape.addEventListener('destroy', () => { ReactDOM.unmountComponentAtNode(reactContainer); }); return shape; };
这种方式让React组件直接嵌入mxGraph的顶点DOM中,组件的事件、Hooks都能正常工作,且每个组件独立维护生命周期。
2. 正确配置React Portal避免被覆盖
之前Portal被覆盖的核心原因是mxGraph会在单元格重新渲染时重建DOM,需要将Portal的目标容器与mxGraph的单元格形状绑定,并在渲染周期中维护容器:
// 监听mxGraph的单元格渲染完成事件 graph.addListener(mxEvent.CELL_RENDERED, (sender, evt) => { const state = evt.getProperty('state'); const cellData = state.cell.value; if (!cellData?.component) return; // 检查是否已存在Portal目标容器,不存在则创建 let portalContainer = state.shape.querySelector('.react-portal-target'); if (!portalContainer) { portalContainer = document.createElement('div'); portalContainer.className = 'react-portal-target'; portalContainer.style.width = '100%'; portalContainer.style.height = '100%'; state.shape.appendChild(portalContainer); } // 使用Portal挂载组件 ReactDOM.createPortal( <cellData.component {...cellData.props} />, portalContainer ); // 绑定销毁逻辑 state.shape.ondestroy = () => { ReactDOM.unmountComponentAtNode(portalContainer); }; });
通过在CELL_RENDERED事件中动态维护Portal容器,确保mxGraph重新渲染单元格时,Portal能正确指向最新的DOM元素,避免被覆盖。
3. 局部更新优化,避免全局重建
为了解决单个组件变更需重建全部HTML的问题,可维护单元格与React组件实例的映射,实现精准更新:
// 存储单元格ID到组件实例的映射 const cellComponentMap = new Map(); // 监听单元格渲染事件,保存组件引用 graph.addListener(mxEvent.CELL_RENDERED, (sender, evt) => { const state = evt.getProperty('state'); const cellId = state.cell.getId(); const cellData = state.cell.value; if (!cellData?.component) return; const container = state.shape.querySelector('.react-portal-target') || document.createElement('div'); if (!state.shape.contains(container)) { container.className = 'react-portal-target'; state.shape.appendChild(container); } // 挂载组件并保存引用(函数组件需通过useImperativeHandle暴露更新方法) ReactDOM.render( <cellData.component {...cellData.props} ref={(ref) => ref && cellComponentMap.set(cellId, ref)} />, container ); state.shape.ondestroy = () => { cellComponentMap.delete(cellId); ReactDOM.unmountComponentAtNode(container); }; }); // 自定义更新函数,仅更新目标组件 function updateCellComponent(cellId, newProps) { const componentRef = cellComponentMap.get(cellId); if (componentRef) { // 假设组件通过useImperativeHandle暴露了update方法 componentRef.update(newProps); } }
当需要更新某个组件时,直接调用updateCellComponent方法,通过组件实例触发局部更新,无需重建整个mxGraph的HTML结构。
4. 内存泄漏防范
必须在单元格被移除或销毁时卸载对应的React组件,除了上述代码中的destroy事件监听,还可以监听mxGraph的CELL_REMOVED事件做兜底:
graph.addListener(mxEvent.CELL_REMOVED, (sender, evt) => { const cell = evt.getProperty('cell'); const cellId = cell.getId(); const componentRef = cellComponentMap.get(cellId); if (componentRef) { // 卸载组件并清理映射 ReactDOM.unmountComponentAtNode(componentRef.container); cellComponentMap.delete(cellId); } });
内容的提问来源于stack exchange,提问作者Nikolay
相关产品推荐
相关产品推荐

