reactjs React流中的自定义节点在创建节点后将其他数据保存到节点

edqdpe6u  于 2023-01-30  发布在  React
关注(0)|答案(3)|浏览(190)

这是我第一次介绍react-flow。我希望创建一个自定义节点,创建后,用户可以在节点中输入信息并保存/显示它。在react-flow documentation on custom nodes中,他们有一个类似的示例,他们创建了一个TextUpdaterNodeconsole.logs是用户输入的。
我没有通过控制台记录它,而是寻找一种方法将信息保存到节点本身并在节点上显示它。例如,如果用户在输入中输入“24,male”并按“enter”键,我希望节点用该信息更新。
我可以用什么方法来做这件事?

1qczuiv0

1qczuiv01#

你要做的事情还不止这些:
您可以在这里看到生动的例子:https://codesandbox.io/s/dank-waterfall-8jfcf4?file=/src/App.js
基本上,您需要:

  • 从“React流渲染器”导入useNodesState;
  • 除了节点的基本定义,您还需要用途:const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  • 然后,必须定义onAdd,如下所示:
const onAdd = useCallback(() => {
   const newNode = {
     id: getNodeId(),
     data: { label: `${state.name} (${state.age})` },
     position: {
       x: 0,
       y: 0 + (nodes.length + 1) * 20
     }
   };
   setNodes((nds) => nds.concat(newNode));
 }, [nodes, setNodes, state.name, state.age]);
  • 您可以包括编辑,非常类似于:
const onEdit = () => {
    setNodes((nds) =>
      nds.map((node) => {
        if (node.id === editState.id) {
          node.data = {
            ...node.data,
            label: `${node.id} - ${editState.name} (${editState.age})`
          };
        }

        return node;
      })
    );
  };
  • 最后画出流程:<ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />

整个代码如下所示:

import React, { useState, useCallback } from "react";
import ReactFlow, {
  ReactFlowProvider,
  useNodesState,
  useEdgesState
} from "react-flow-renderer";

import "./styles.css";

const getNodeId = () => `randomnode_${+new Date()}`;

const initialNodes = [
  { id: "1", data: { label: "Node 1" }, position: { x: 100, y: 100 } },
  { id: "2", data: { label: "Node 2" }, position: { x: 100, y: 200 } }
];

const initialEdges = [{ id: "e1-2", source: "1", target: "2" }];

const FlowExample = () => {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges] = useEdgesState(initialEdges);
  const [state, setState] = useState({ name: "", age: "" });

  const onAdd = useCallback(() => {
    const newNode = {
      id: getNodeId(),
      data: { label: `${state.name} (${state.age})` },
      position: {
        x: 0,
        y: 0 + (nodes.length + 1) * 20
      }
    };
    setNodes((nds) => nds.concat(newNode));
  }, [nodes, setNodes, state.name, state.age]);

  return (
    <div>
      Name:{" "}
      <input
        type="text"
        onChange={(e) => {
          setState((prev) => ({ ...prev, name: e.target.value }));
        }}
      />
      Age:{" "}
      <input
        type="text"
        onChange={(e) => {
          setState((prev) => ({ ...prev, age: e.target.value }));
        }}
      />
      <button onClick={onAdd}>add node</button>
      <div style={{ width: "500px", height: "500px" }}>
        <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
      </div>
    </div>
  );
};

export default () => (
  <ReactFlowProvider>
    <FlowExample />
  </ReactFlowProvider>
);

此外,经过编辑:

import React, { useState, useCallback } from "react";
import ReactFlow, {
  ReactFlowProvider,
  useNodesState,
  useEdgesState
} from "react-flow-renderer";

import "./styles.css";

const getNodeId = () => `${String(+new Date()).slice(6)}`;

const initialNodes = [
  { id: "1", data: { label: "Node 1" }, position: { x: 100, y: 100 } },
  { id: "2", data: { label: "Node 2" }, position: { x: 100, y: 200 } }
];

const initialEdges = [{ id: "e1-2", source: "1", target: "2" }];

const FlowExample = () => {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges] = useEdgesState(initialEdges);
  const [state, setState] = useState({ name: "", age: "" });
  const [editState, setEditState] = useState({ id: "", name: "", age: "" });

  const onEdit = () => {
    setNodes((nds) =>
      nds.map((node) => {
        if (node.id === editState.id) {
          node.data = {
            ...node.data,
            label: `${node.id} - ${editState.name} (${editState.age})`
          };
        }

        return node;
      })
    );
  };

  const onAdd = () => {
    const id = getNodeId();
    const newNode = {
      id,
      data: { label: `${id} - ${state.name} (${state.age})` },
      position: {
        x: 0,
        y: 0 + (nodes.length + 1) * 20
      }
    };
    setNodes((nds) => nds.concat(newNode));
  };

  return (
    <div>
      Name:{" "}
      <input
        type="text"
        onChange={(e) => {
          setState((prev) => ({ ...prev, name: e.target.value }));
        }}
      />
      Age:{" "}
      <input
        type="text"
        onChange={(e) => {
          setState((prev) => ({ ...prev, age: e.target.value }));
        }}
      />
      <button onClick={onAdd}>add node</button>
      <br />
      Id:{" "}
      <input
        type="text"
        onChange={(e) => {
          setEditState((prev) => ({ ...prev, id: e.target.value }));
        }}
      />
      Name:{" "}
      <input
        type="text"
        onChange={(e) => {
          setEditState((prev) => ({ ...prev, name: e.target.value }));
        }}
      />
      Age:{" "}
      <input
        type="text"
        onChange={(e) => {
          setEditState((prev) => ({ ...prev, age: e.target.value }));
        }}
      />
      <button onClick={onEdit}>Edit node</button>
      <div style={{ width: "500px", height: "500px" }}>
        <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
      </div>
    </div>
  );
};

export default () => (
  <ReactFlowProvider>
    <FlowExample />
  </ReactFlowProvider>
);

文档中更有用的示例是:

但是你必须删除所有额外的信息(还有,你可以用它来更深入!)

7vux5j2d

7vux5j2d2#

我设法想出了一个解决方案来创建这样一个自定义节点,允许您输入,保存和显示信息。我试图包括相关的信息和代码块,我用下面。

自定义节点

import { useCallback } from 'react';
import { Handle, Position} from 'react-flow-renderer';
const handleStyle = { left: 10 };
//Custom node requires props of data to be passed to it.
function CustomNode({ data }) {    
    let serviceType = "offered";
    //This handles pressing enter inside the description
    const handleKeyDown = (evt) => {
        if (evt.key === "Enter") {
            //Check if empty string
            if (evt.target.value.length !== 0) {
                //This code is because services are either offered or borrowed.
              if (serviceType === "offered") {
                data.serviceOffered.push(evt.target.value);
              } else if (serviceType === "borrowed") {
                data.serviceBorrowed.push(evt.target.value);
              }
                //Clearing input after pressing enter
              evt.currentTarget.value = "";
            }
        }

  };
  const onChange = useCallback((evt) => {
    //Update service type without pressing enter
    serviceType = evt.target.value;
  });

  return (
    <div className="text-updater-node">
      <Handle type="target" position={Position.Top} />
      <div>
        <p>Entity</p>
        <label htmlFor="text"><p className='nodeTitle'>{data.label}</p></label>
        <input id="text" name="text" onKeyDown={handleKeyDown} /> 
        <select name="type" onChange={onChange}>
          <option value="offered" >Offered </option>
          <option value="borrowed">Borrowed</option>
        </select>
        <div className="info">
            {/* This is where the description information is displayed. It checks if it is empty, if not it loops through and displays it.  */}
            <h2>Service Borrowed</h2>
            <ul>
              {data.serviceBorrowed.length? data.serviceBorrowed.map(service => (<li key={service}>{service}</li>)) : <span></span>} 
            </ul>
            <h2>Service Offered</h2> 
            <ul>
              {data.serviceOffered.length? data.serviceOffered.map(service => (<li key={service}>{service}</li>)) : <span></span>}
            </ul>
        </div>
      </div>
      <Handle type="source" position={Position.Bottom} id="a" style={handleStyle} />
      <Handle type="source" position={Position.Bottom} id="b" />
    </div>
  );
}
export default CustomNode;

我有一个reactFlow父组件,代码块如下,重要的是设置react flow的自定义节点类型,并传入一个object,其中包含要渲染的节点和边的信息。

import { Fragment, useCallback, useState } from "react";
import ReactFlow, {
  addEdge,
  applyEdgeChanges,
  applyNodeChanges,
} from "react-flow-renderer";
import initialNodes from "../data/nodes"; //This both ended up being empty file
import initialEdges from "../data/edges"; //This both ended up being empty file
import CustomNode from "./customNode";
import "./customNode.css";
//Set nodetype as Custom node, IMPORTANT!
const nodeTypes = { customNode: CustomNode };

function Flow() {
  const defaultEdgeOptions = { animated: true };
  //Input Elements
  const [name, setName] = useState("");
  const addNode = () => {
    setNodes((e) =>
      e.concat({
        id: (e.length + 1).toString(),
        data: { label: `${name}`, serviceOffered: [], serviceBorrowed: [] },
        position: { x: 0, y: 0 },
        type: "customNode",
      })
    );
  };
  //Nodes and edges containing information of the nodes and edges
  const [nodes, setNodes] = useState(initialNodes);
  const [edges, setEdges] = useState(initialEdges);
  //Boiler plate code for reactFlow
  const onNodesChange = useCallback(
    (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
    [setNodes]
  );
  const onEdgesChange = useCallback(
    (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
    [setEdges]
  );
  const onConnect = useCallback(
    (connection) => setEdges((eds) => addEdge(connection, eds)),
    [setEdges]
  );

  return (
    <Fragment>
      <Row>
        <Col lg={9}>
          <ReactFlow
            className="Canvas mt-1 border border-secondary rounded"
            nodes={nodes} //Node information is passed here
            edges={edges} //Edges information is passed here
            onNodesChange={onNodesChange}
            onEdgesChange={onEdgesChange}
            onConnect={onConnect}
            defaultEdgeOptions={defaultEdgeOptions}
            style={{ width: "100%", height: "80vh" }}
            fitView
            nodeTypes={nodeTypes}
          />
        </Col>
      </Row>
    </Fragment>
  );
}

export default Flow;

我在node.jsdata属性中添加了更多信息。它最终被初始化为空,但这个模板应该有助于理解我如何保存node的信息。edge遵循react-flow文档中显示的标准格式。

export default [
    // {
    //   id: '1',
    //   type: 'customNode',
    //   data: { label: 'Input Node', info: [{id:1, action:"Everything is burning"}, {id:2, action:"I'm fine"}], noOfActions:2 },
    //   position: { x: 250, y: 25 },
    // },
  ];

希望这对你有用!

3mpgtkmj

3mpgtkmj3#

可接受的答案是关于修改组件的属性,这不是React方式。该代码可能很容易中断。还有其他方式将回调带到自定义节点。
1.将回调放入节点的数据中
这来自React流程文件:https://reactflow.dev/docs/examples/nodes/custom-node/

setNodes([
      ...
      {
        id: '2',
        type: 'selectorNode',
        data: { onChange: onChange, color: initBgColor },
      ...

缺点:动态修改或创建新节点时需要格外注意
1.或动态定义自定义类型
在这种方法中,您将节点数据和行为问题分开。
我使用TypeScript是为了显示我们在此过程中操作的数据类型。
首先,使用回调扩展自定义节点属性:

import {NodeProps} from "react-flow-renderer/dist/esm/types/nodes";

// by default, custom node is provisioned with NodeProps<T>
// we extend it with additional property
export type CustomNodeProps = NodeProps<CustomData> & {
  onClick: (id: string) => void
}

function CustomNode(props: CustomNodeProps) {
  return <button onClick={() => props.onClick(props.id)}>Do it</button>
}

然后创建提供回调的新构造函数,并使用记忆化将其放入自定义节点Map中:

function Flow() {
  const [graph, dispatchAction] = useReducer(...);
  ...

  // useMemo is neccessary https://reactflow.dev/docs/guides/troubleshooting/#it-looks-like-you-have-created-a-new-nodetypes-or-edgetypes-object-if-this-wasnt-on-purpose-please-define-the-nodetypesedgetypes-outside-of-the-component-or-memoize-them
  const nodeTypes = useMemo(() => {
    return {
      custom: (props: NodeProps<CustomData>) => {
        return CustomNode({...props, onClick: (id: string) => {
          dispatchAction({
            type: 'customNodeButtonClicked',
            nodeId: id,
          })
        }})
      }
    }
  }, [])

  return (
    <>
      <ReactFlow nodeTypes={nodeTypes} ... />
    </>
  );
}

相关问题