簡體   English   中英

使用 REACTFLOW 的自定義節點; 創建節點后,將其他數據保存到節點

[英]Custom nodes with REACTFLOW; saving additional data to a node once the node is created

這是我對react-flow的第一次介紹。 我希望創建一個自定義節點,創建后,用戶可以在節點中輸入信息並保存/顯示它。 自定義節點上的react-flow文檔中,他們有一個類似的示例,他們創建了一個TextUpdaterNode ,該console.logs用戶輸入。

我沒有通過控制台記錄它,而是在尋找一種將信息保存到節點本身並將其顯示在節點上的方法。 例如,如果用戶在輸入中輸入24, male並按下enter ,我希望使用下面的信息更新節點。

go 有什么方法可以做到這一點?

你想要做的還不止這些:

你可以在這里看到活生生的例子:https://codesandbox.io/s/dank-waterfall-8jfcf4?file=/src/App.js

基本上,您需要:

  • 從 'react-flow-renderer' 導入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>
);

文檔中一個更有幫助的例子是:

但是您必須刪除所有額外的信息(另外,您可以使用它來更深入地了解 go!)

自定義節點

我設法想出了一個解決方案來創建這樣一個允許您輸入、保存和顯示信息的自定義節點。 我試圖包括相關信息和我在下面使用的代碼塊。

自定義節點

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 },
    // },
  ];

我希望這很有用!

接受的答案是關於修改組件的屬性,這不是 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>
}

然后創建提供回調的新構造函數,並使用記憶化將其放入自定義節點映射中:

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} ... />
    </>
  );
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM