didi / LogicFlow

专注于业务自定义的流程图编辑框架,支持实现脑图、ER图、UML、工作流等各种图编辑场景。A flow chart editing framework focusing on business customization.
https://site.logic-flow.cn
Apache License 2.0
8.25k stars 1.09k forks source link

[Bug Report]: 拖动react自定义节点组件到画布中,使用Portal 方式渲染,拖拽完成后出现重复创建元素导致的key重复问题,而且在进入画布的时候也会自动添加一个虚拟节点到虚拟dom树中 #1866

Closed jiujiu-123 closed 3 weeks ago

jiujiu-123 commented 3 weeks ago

发生了什么?

react中 使用函数式组件定义react自定义节点,并使用Portal 方式渲染组件自定义组件 我将自定义组件通过dnd.startDrag()的方式拖动到画布中 即使我没有松开鼠标 他刚到画布中就会在reactDOM树中保存一个节点,当我松开鼠标时他会同时保存两个id一样的节点且报错key重复, 只要我不用Portal 方式渲染组件就不会存在这个问题 是不是Portal.getProvider()内部的useReducer方法存在问题

index.js:1 Warning: Encountered two children with the same key, 5a0c618a-3546-4f38-b8c3-fd32c756f2d9. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.

这个是拖拽逻辑 const MouseDown = (e: MouseEvent,item: any) => { e.stopPropagation() props.flow?.dnd.startDrag({ type: item.type, label: ${(e.target as HTMLElement).textContent}, text: ${(e.target as HTMLElement).textContent}, properties: { name: item.type, width: item.properties?.width || 100, height: item.properties?.height || 100,

    style: {
      width: "100px",
      height: '100px',
      border: '1px solid #000000',
      stroke:"blue",
      strokeLinecap: 'round',
    }
  },
})

}

这个是展示画布组件的jsx export const CanvasContext = createContext<LogicFlow | null>(null); const LFReactPortalProvider = Portal.getProvider();

<div className="flex flex-row w-[100%] h-[99vh]">
  <div
    style={{ borderRight: '1px solid #f4f4f4' }}
    className="flex h-[100%] w-[300px] pt-5 bg-[#ffffff]"
  >
    <MaterialList flow={flow} />
  </div>

  <div>
    {/*<CanvasContext.Provider >*/}
      <LFReactPortalProvider />
    {/*</CanvasContext.Provider>*/}
  </div>
  <div className="flex-1" ref={containerFlowRef}></div>

</div>

这是自定义的react节点 import { ReactNodeProps, ReactNodeView } from '@logicflow/react-node-registry'; import { FC, memo, useContext } from 'react'; import { CanvasContext } from '@/pages/editor';

// 自定义 React 组件 const NodeComponent: FC = ({ node }) => { // const flow = useContext(CanvasContext) // if(!flow || !node) return; // flow.getDataById(node.id) const data = node.getData(); if (!data.properties) data.properties = {};

const confirmUpdate = ReactNodeView.prototype.confirmUpdate.bind(node) // 重写更新逻辑 ReactNodeView.prototype.confirmUpdate = ()=>{ // console.log(13131); } return ( <div style={node.getProperties().style} key={new Date().getTime()}>

{data.properties.name as string}圆形
</div>

); };

export default memo(NodeComponent);

logicflow/core版本

2.0.3

logicflow/extension版本

2.0.6

logicflow/engine版本

No response

浏览器&环境

Chrome

github-actions[bot] commented 3 weeks ago

该 issue 作为 Bug Report 所提供信息的不充分,被暂时关闭了。请修改 issue 以提供最小可复现示例(可以通过以下方式:1. 在任意在线编码平台如 codesanbox 编写示例。将其保存到自己空间,然后贴上链接。2. 在自己 github 中创建一个最简单的示例,然后贴上 github 链接。3. 删除项目中的 node_modules 部分,打包项目,并拖拽到 issue 输入框中上传(或提供远程可下载地址))来重启 issue。

github-actions[bot] commented 3 weeks ago

该 issue 作为 Bug Report 所提供信息的不充分,被暂时关闭了。请修改 issue 以提供最小可复现示例(可以通过以下方式:1. 在任意在线编码平台如 codesanbox 编写示例。将其保存到自己空间,然后贴上链接。2. 在自己 github 中创建一个最简单的示例,然后贴上 github 链接。3. 删除项目中的 node_modules 部分,打包项目,并拖拽到 issue 输入框中上传(或提供远程可下载地址))来重启 issue。

gismanli commented 3 weeks ago

https://github.com/didi/LogicFlow/blob/master/packages/react-node-registry/src/view.ts#L45

这里有BUG,直接用的model.id,应该用生成的targetId

我自己重写了ReactNodeView,可以参考:

import { createElement } from "react";
import { createPortal } from "react-dom";
import { HtmlNode } from "@logicflow/core";
import { Portal, reactNodesMap } from "@logicflow/react-node-registry";

export class CustomReactNodeView extends HtmlNode {
  getText() {
    return null;
  }

  protected targetId() {
    return `${this.props.graphModel.flowId}:${this.props.model.id}`;
  }

  componentWillUnmount(): void {
    Portal.disconnect(this.targetId());
    super.componentWillUnmount();
  }

  setHtml(rootEl: SVGForeignObjectElement) {
    const { model, graphModel } = this.props;

    const key = this.targetId();

    let el = rootEl.querySelector(".custom-react-node-content");
    if (!el) {
      el = document.createElement("div");
      el.className = "custom-react-node-content";
      rootEl.appendChild(el);
    }

    console.log("render ===>>>", key, model.type, el);

    const { component: Component } = reactNodesMap[model.type];
    Portal.connect(
      key,
      createPortal(
        createElement(Component, {
          node: model,
          graph: graphModel,
        }),
        el,
        key
      )
    );
    model.updateStyles({
      height: el.clientHeight,
    });
  }
}