在React Flow Renderer中,节点是构建工作流界面的基本组成部分之一。本文将介绍如何创建和定制不同类型的节点,以满足您的工作流需求。
提供github地址供大家参考。地址: React-Flow-Renderer
节点是工作流中的核心元素,它们代表了不同的操作、条件或数据处理步骤。在React Flow Renderer中,节点是可拖动和可配置的,允许用户根据其需求创建自定义的节点。
在项目中使用Redux时,需要创建一个reducer来管理状态。以下是一个示例Reducer,用于处理与节点相关的状态和操作
// consatants.ts
// 规则链
export const OPEN_RULE_CHAIN_MODAL = 'OPEN_RULE_CHAIN_MODAL'; // 打开规则链模态框的动作类型
export const CLOSE_RULE_CHAIN_MODAL = 'CLOSE_RULE_CHAIN_MODAL'; // 关闭规则链模态框的动作类型
export const SET_RULE_CHAIN_NODE = 'SET_RULE_CHAIN_NODE'; // 设置规则链节点的动作类型
// ruleChainAction.ts
import * as Actions from '../constant';
// 打开模态框的动作
export const openModal = (data: any) => ({
type: Actions.OPEN_RULE_CHAIN_MODAL,
data
});
// 关闭模态框的动作
export const closeModal = (data: any) => ({
type: Actions.CLOSE_RULE_CHAIN_MODAL,
data
});
// 设置规则链节点的动作
export const setRuleChainNode = (data: any) => ({
type: Actions.SET_RULE_CHAIN_NODE,
data
});
// ruleChainReducer.ts
import { CLOSE_RULE_CHAIN_MODAL, OPEN_RULE_CHAIN_MODAL, SET_RULE_CHAIN_NODE } from '../constant';
const initState = {
nodes: [],
// 弹窗信息
modalConfig: {
visible: false,
node: null
}
};
export default function ruleChainReducer(
state = {
...initState
},
action: { type: any; data: any }
) {
// 从action对象中获取:type,data
const { type, data } = action;
// 根据type决定加工数据
switch (type) {
case OPEN_RULE_CHAIN_MODAL:
return {
...state,
modalConfig: {
visible: true,
node: data
}
};
case CLOSE_RULE_CHAIN_MODAL:
return {
...state,
modalConfig: {
visible: false,
node: null
}
};
case SET_RULE_CHAIN_NODE:
return {
...state,
nodes: data
};
default:
return state;
}
}
首先,让我们看一下如何创建一个节点组件。每个节点通常都有自己的特性和配
置选项。在你的示例中,我们有一个名为Index
的节点组件,它接收不同的节点类型和数据,并以不同的方式呈现它们。
// /Node/index.tsx
import React, { useState } from 'react';
import { Handle, Position, useNodes, useReactFlow } from 'react-flow-renderer';
import { IconButton, Menu, MenuItem } from '@mui/material';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import { useDispatch } from 'react-redux';
import { OPEN_RULE_CHAIN_MODAL, SET_RULE_CHAIN_NODE } from '@/store/constant';
interface EditMenuProps {
anchorEl: HTMLButtonElement | null;
open: boolean;
handleClose: () => void;
node: any;
}
// 编辑菜单
const EditMenu = (props: EditMenuProps) => {
const dispatch = useDispatch();
const { setNodes } = useReactFlow();
const nodes = useNodes();
const { anchorEl, open, handleClose, node } = props;
const edit = () => {
// 打开编辑模态框
dispatch({
type: OPEN_RULE_CHAIN_MODAL,
data: node
});
handleClose();
};
const remove = () => {
setNodes(nodes.filter((item) => item.id !== node.id));
// 更新节点对象并分发action
dispatch({
type: SET_RULE_CHAIN_NODE,
data: nodes.filter((item) => item.id !== node.id)
});
};
return (
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem key="1" onClick={edit}>
Edit
</MenuItem>
<MenuItem key="2" onClick={remove}>
Delete
</MenuItem>
</Menu>
);
};
export const NodeType = {
relation: 'relation',
input: 'input',
filter: 'filter',
action: 'action',
flow: 'FLOW'
};
const Index = (props: any) => {
const { ...currentNode } = props;
// Menu用的方法
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handleClick = (event: any) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const classMap = new Map([
['ACTION', 'relation-node'],
['input', 'input-node'],
['FILTER', 'filter-node'],
['ENRICHMENT', 'enrichment-node'],
['TRANSFORMATION', 'transformation-node'],
['EXTERNAL', 'external-node'],
['FLOW', 'flow-node']
]);
return (
<div
className={`relation-node ${
classMap.get(currentNode.type) || 'default-node'
}`}
>
<div className="relation-node-title">
{currentNode.type !== NodeType.input && currentNode.data.label &&
<>
{currentNode.data.label}
<br />
</>
}
{currentNode.data.name}
</div>
<div
className="relation-node-action"
style={{
display: 'flex',
alignItems: 'flex-end',
justifyContent: 'center'
}}
>
<IconButton aria-label="delete" size="small" onClick={handleClick}>
<MoreVertIcon fontSize="inherit" />
</IconButton>
<EditMenu
anchorEl={anchorEl}
open={open}
handleClose={handleClose}
node={currentNode}
{...props}
/>
</div>
{/* 提供一个入口和一个出口 */}
{currentNode.type !== NodeType.input &&
<Handle
type="target"
position={Position.Left}
isConnectable={currentNode.isConnectable}
/>
}
<Handle
type="source"
position={Position.Right}
isConnectable={currentNode.isConnectable}
/>
</div>
);
};
export default React.memo(Index);
上述代码展示了一个节点组件的基本结构,其中包括节点的外观和操作。
我们希望节点能够编辑和删除,这时候我们就需要一个可编辑的模态框,下面是一个简单的模态框的代码。
首先我们创建一个自定义的表单
// modal/RelationNodeForm.tsx
import React, { useImperativeHandle } from 'react';
import { connect, useDispatch } from 'react-redux';
import { useForm } from 'react-hook-form';
import {
FormContainer,
TextFieldElement,
TextareaAutosizeElement
} from 'react-hook-form-mui';
import { useNodes, useReactFlow } from 'react-flow-renderer';
import { SET_RULE_CHAIN_NODE } from '@/store/constant';
interface RelationNodeFormProps {
modalConfig: any;
events: any;
nodes: any;
}
function Index(props: RelationNodeFormProps) {
const { modalConfig, events, nodes } = props; // 从props中解构获取所需的变量
const { setNodes } = useReactFlow(); // 使用useReactFlow钩子获取setNodes函数
const flowNodes = useNodes(); // 使用useNodes钩子获取当前节点列表
const initialValues = nodes.find(
(node: any) => node.id === modalConfig.node?.id
); // 根据modalConfig中的node.id查找对应的初始值
const dispatch = useDispatch(); // 获取dispatch函数
const formContext = useForm<any>({
defaultValues: {
name: '',
remark: ''
},
mode: 'all' // 验证模式切换为all
});
/**
* 构建更新后的节点对象
* @param {any} data - 表单数据
* @param {any} node - 节点数据
*/
function buildUpdateNode(data: any, node: any) {
return {
...node,
name: data.name,
description: data.remark
};
}
function submit() {
// 获取表单数据
const data = formContext.watch();
// 更新节点对象并分发action
dispatch({
type: SET_RULE_CHAIN_NODE,
data: nodes.map((node: any) =>
node.id === modalConfig.node.id ? buildUpdateNode(data, node) : node
)
});
// 更新节点数组
setNodes(
flowNodes.map((node: any) =>
node.id === modalConfig.node.id ? buildUpdateNode(data, node) : node
)
);
}
// 暴露submit的方法
useImperativeHandle(
events,
() => {
return {
submit
};
},
[]
);
React.useEffect(() => {
formContext.reset({
name: initialValues?.data.name,
remark: initialValues?.additionalInfo.description
});
}, []);
return (
<FormContainer formContext={formContext}>
{/* 节点名称 */}
<TextFieldElement
required
margin="normal"
fullWidth
label={'name'}
name="name"
size="small"
variant="outlined"
/>
{/* 节点描述 */}
<TextareaAutosizeElement
rows={2}
margin="normal"
fullWidth
label={'description'}
name="remark"
size="small"
variant="outlined"
/>
</FormContainer>
);
}
// redux获取当前flow的数据
const mapStateToProps = (state: any) => {
const { modalConfig, nodes } = state.ruleChainReducer;
return {
modalConfig,
nodes
};
};
export default connect(mapStateToProps)(Index);
接着我们创建一个模态框来包含这个表单
// modal/Index.tsx
// Modal/index.jsx
import React, { useRef } from 'react';
import RelationNodeForm from './RelationNodeForm';
import { connect, useDispatch } from 'react-redux';
import { EnhancedDialog } from '@/components/EnhancedDialog';
import { CLOSE_RULE_CHAIN_MODAL } from '@/store/constant';
interface ModalProps {
modalConfig: any;
}
export function Index(props: ModalProps) {
const formRef = useRef();
const { modalConfig } = props;
const dispatch = useDispatch();
const Component = RelationNodeForm;
const handleOk = () => {
const current = formRef.current as any;
// 组件内部需要暴露一个 submit 方法
current?.submit();
dispatch({ type: CLOSE_RULE_CHAIN_MODAL });
};
const handleCancel = () => dispatch({ type: CLOSE_RULE_CHAIN_MODAL });
return (
//这是自定义模态框,你可以使用你熟悉或者项目中使用的组件
<EnhancedDialog
title={`${modalConfig.node?.type} - ${modalConfig.node?.data.label}`}
visible={modalConfig.visible}
onOk={handleOk}
onCancel={handleCancel}
maxWidth="xs"
>
{Component && <Component events={formRef} />}
</EnhancedDialog>
);
}
// redux获取当前flow的数据
const mapStateToProps = (state: any) => {
const { modalConfig } = state.ruleChainReducer;
return {
modalConfig
};
};
export default connect(mapStateToProps)(Index);
本文深入介绍了如何创建和定制节点,在React Flow Renderer中,节点是构建工作流界面的关键部分。通过定制节点组件和注册节点类型,您可以轻松扩展工作流的功能和外观,以满足项目的需求。
在下一篇博客中,我们将继续探讨React Flow Renderer的画布功能和连接线功能。敬请期待!