cyf-react-flow
v1.2.14
Published
一个基于 React 的流程图/思维导图编辑器组件库,支持:
Readme
cyf-react-flow
一个基于 React 的流程图/思维导图编辑器组件库,支持:
- 节点拖拽、缩放、多选、对齐、分组
- 普通连线(曲线/折线)、虚线、箭头、流动动画
- 自由直线箭头(可旋转、改变长度)
- 画布缩放、平移、背景网格
本仓库既是组件库源码,又可以本地直接
npm start作为 Demo 运行。
安装
npm install cyf-react-flow
# 或
yarn add cyf-react-flow要求:
- React
>=18 - React DOM
>=18
react 和 react-dom 在库里是 peerDependencies,会复用你项目中的 React,不会再额外装一份。
快速上手
在你的项目中(例如 src/App.tsx):
import "cyf-react-flow/index.css";
import React, { useState } from "react";
import { FlowEditorReact, Nodes, Connections } from "cyf-react-flow";
const defaultNodeTypes = [
{ type: "start", label: "开始", width: 80, height: 80, color: "#3b82f6" },
{ type: "decision", label: "判断", width: 140, height: 60, color: "#f97316" },
{ type: "custom-card", label: "插槽", width: 50, height: 30, color: "#fbbf24" }
];
function App() {
const [nodes, setNodes] = useState<Nodes[]>([]);
const [connections, setConnections] = useState<Connections[]>([]);
return (
<div style={{ width: "100vw", height: "100vh" }}>
<FlowEditorReact
nodeTypes={defaultNodeTypes}
nodesValue={nodes}
connections={connections}
isPview={false}
isZoom
isPanning
isConnectionType
isShowConfig
isCzsm
isRightConfig={false}
onNodesChange={setNodes}
onConnectionsChange={setConnections}
renderNodeContent={(node) =>
node.type === "custom-card" ? <div>自定义节点内容:{node.label}</div> : null
}
/>
</div>
);
}
export default App;关键点:
- 必须单独引入样式:
import "cyf-react-flow/index.css"; FlowEditorReact是具名导出:import { FlowEditorReact } from "cyf-react-flow";Nodes、Connections为 TypeScript 类型导出,可选用。
我能做什么(能力速览)
使用一个组件 FlowEditorReact,你可以:
- 作为「纯画布引擎」使用,只关心
nodes/connections数据; - 通过插槽定制:
- 左侧面板(
renderLeft)—— 自己画节点列表 / 模板中心; - 节点内容(
renderNodeContent)—— 在节点里放任意 React 组件; - 顶部工具栏(
renderToolbar)—— 自己写放大缩小、保存、发布等按钮; - 右侧配置(
renderRightConfig)—— 选中节点时在右侧展示配置表单;
- 左侧面板(
- 完全受控地管理数据:
onNodesChange/onConnectionsChange拿到所有操作结果,存数据库或和后端同步。
下面的章节会按「从简单到进阶」的方式介绍这些能力。
API 说明
组件导出
入口文件:dist/index.build.js(源码对应 src/index.build.ts)
导出内容:
FlowEditorReact:流程图编辑器组件FlowEditorReactProps:组件 Props 类型FlowEditorPublicApi:对外暴露的内部状态和方法集合类型Nodes、Connections:节点和连线的数据结构类型
在 TS 项目中也可以这样单独引入类型:
import type { Nodes, Connections } from "cyf-react-flow";
// 或
// import type { Nodes, Connections } from "cyf-react-flow/type";FlowEditorReact Props 概览
组件定义:src/ulits/FlowEditorReact.tsx:19-40
nodeTypes: NodeType[](必填)
左侧节点面板的配置,控制能拖入画布的节点类型:type NodeType = { type: string; label: string; width: number; height: number; color: string; };nodesValue?: Nodes[]
初始节点列表。配合onNodesChange可以做成受控组件。connections?: Connections[]
初始连线列表。配合onConnectionsChange做受控。onNodesChange?: (nodes: Nodes[]) => void
节点变更时触发(新增、拖拽、缩放、多选修改等)。containerColor?: #fff
画外面背景颜色,默认#fff。onConnectionsChange?: (connections: Connections[]) => void
连线变更时触发(新增、删除、样式修改、自由箭头调整等)。isPview?: boolean
是否只读预览模式(true 时禁止编辑操作)。editorConfig?: { hideSidebar?: boolean; disableBackspaceDelete?: boolean }
编辑器行为配置:hideSidebar:是否隐藏左侧节点面板(包含nodeTypes列表与renderLeft区域),默认false。disableBackspaceDelete:是否禁用 Backspace 删除选中内容(用于避免误删/触发浏览器回退),默认false。
isPanning?: boolean
是否允许平移画布。panRequiresSpace?: boolean
平移画布是否需要按住空格键。默认false(空白处直接左键拖动即可平移,此时框选需要按住 Ctrl 在空白处拖拽);设为true时需要「按住空格 + 左键拖动」才能平移(此时空白处直接拖拽为框选)。isZoom?: boolean
是否允许缩放画布。canvasScale?: number
画布缩放比例(受控)。传入后以该值为准;配合onCanvasScaleChange/onCanvasTransformChange在外部保存并回灌即可实现“缩放受控”。canvasOffset?: { x: number; y: number }
画布平移偏移(受控,单位 px)。传入后以该值为准;配合onCanvasOffsetChange/onCanvasTransformChange可实现“拖拽位置受控”。onCanvasScaleChange?: (scale: number) => void
画布缩放变化时触发(滚轮缩放、调用内置缩放按钮、调用methods.zoomIn/zoomOut/resetZoom等)。onCanvasOffsetChange?: (offset: { x: number; y: number }) => void
画布平移偏移变化时触发(拖拽平移、缩放回推偏移等)。onCanvasTransformChange?: (transform: { scale: number; offset: { x: number; y: number } }) => void
画布缩放或平移任一变化时触发(便于一次性同步到外部 store)。canvasSize?: { width: number; height: number }
逻辑画布尺寸,和右侧配置联动。width?: number/height?: number
组件外层容器尺寸(一般保持全屏即可)。bg?: string/bgColor?: string
背景图片或背景颜色。dotColor?: string/gridColor?: string
点状背景的颜色(默认黑色)或网格背景的颜色(默认rgba(0, 0, 0, 0.1))。gridSize?: number/dotSpacing?: number
网格间距或点状间距(默认值均为40)。backgroundType?: "grid" | "dots" | "color" | "none"
画布背景类型:grid(网格)、dots(点状)、color(纯色)、none(无)。默认值为grid。isConnectionType?: boolean
是否显示顶部的「曲线 / 折线」切换。isShowConfig?: boolean
是否显示右侧配置面板。isCzsm?: boolean
是否在右侧显示操作说明列表。isRightConfig?: boolean
是否开启右侧自定义配置区域。renderLeft?: () => ReactNode
左侧自定义区域插槽(可以放自定义工具、额外可拖拽元素等)。renderNodeContent?: (node: Nodes) => ReactNode
节点内部插槽,可以按节点类型渲染不同内容。renderRightConfig?: (selectNode: Nodes | null, helpers: { setNodeConfig: (id: string, key: string, value: any) => void }) => ReactNode
选中单个节点时,右侧自定义配置区域。通过helpers.setNodeConfig可以直接修改当前节点的数据。renderToolbar?: (api: FlowEditorPublicApi) => ReactNode
顶部工具栏插槽。传入后会完全替换内置的「缩放 / 重置 / 撤销重做 / 图层 / 操作说明」工具条,并拿到内部公开的state、methods、refs、controller以便在外部直接调用。
editorConfig 示例
<FlowEditorReact
nodeTypes={defaultNodeTypes}
nodesValue={nodes}
connections={connections}
editorConfig={{ hideSidebar: true, disableBackspaceDelete: true }}
/>自定义左侧面板、顶部工具栏和右侧配置
1. 自定义左侧拖拽节点(renderLeft)
renderLeft 可以用来自定义左侧面板的内容,例如增加一个自定义节点入口,只要在 onDragStart 中写入节点配置即可:
<FlowEditorReact
nodeTypes={defaultNodeTypes}
nodesValue={nodes}
connections={connections}
renderLeft={() => (
<div
draggable
onDragStart={(e) => {
const nodeData = {
type: "slotName",
label: "自定义节点",
width: 140,
height: 45,
color: "#fff",
};
e.dataTransfer.setData("text/plain", JSON.stringify(nodeData));
e.dataTransfer.effectAllowed = "copy";
}}
>
自定义节点
</div>
)}
onNodesChange={setNodes}
onConnectionsChange={setConnections}
/>当你把这个元素拖到画布上并松手时,内部会从 dataTransfer.getData("text/plain") 解析出 nodeData,在鼠标位置创建一个对应的节点。
2. 自定义顶部工具栏并直接调用内部方法(renderToolbar)
renderToolbar 用于替换默认的顶部工具栏,它接收一个 FlowEditorPublicApi 对象,包含内部的 state、methods、refs、controller,可以在外部直接调用内部方法或读取状态:
import {
FlowEditorReact,
type FlowEditorPublicApi,
} from "cyf-react-flow";
function App() {
const [nodes, setNodes] = useState<Nodes[]>([]);
const [connections, setConnections] = useState<Connections[]>([]);
return (
<FlowEditorReact
nodeTypes={defaultNodeTypes}
nodesValue={nodes}
connections={connections}
onNodesChange={setNodes}
onConnectionsChange={setConnections}
renderToolbar={(api: FlowEditorPublicApi) => {
const { methods, state, controller } = api;
const scale = state.canvasScale || 1;
const selectNode =
controller?.state?.selectNodes?.value || null;
return (
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
padding: "4px 8px",
borderBottom: "1px solid #eee",
}}
>
<button onClick={() => methods.zoomIn()}>放大</button>
<button onClick={() => methods.zoomOut()}>缩小</button>
<button onClick={() => methods.resetZoom()}>重置</button>
<button onClick={() => methods.undo()}>撤销</button>
<button onClick={() => methods.redo()}>重做</button>
<span style={{ marginLeft: 12 }}>
当前缩放:{Math.round(scale * 100)}%
</span>
{selectNode && (
<span style={{ marginLeft: 12 }}>
选中节点:{selectNode.label}
</span>
)}
</div>
);
}}
/>
);
}不传 renderToolbar 时,会使用默认工具栏;传入 renderToolbar 后,默认工具栏不再渲染,由你完全接管顶部区域。
上面的示例中也演示了如何在 App 层拿到「当前选中节点」:
- 顶部展示:
selectNode && <span>选中节点:{selectNode.label}</span>; - 或在按钮点击时打印:
console.log("当前选中节点:", selectNode)。
3. 自定义右侧配置面板(renderRightConfig)
renderRightConfig 接收两个参数:
renderRightConfig?: (
selectNode: Nodes | null,
helpers: { setNodeConfig: (id: string, key: string, value: any) => void }
) => ReactNode;selectNode:当前选中的单个节点对象(没有选中或多选时为null)helpers.setNodeConfig(id, key, value):直接修改指定节点某个字段的值,并触发画布和外部onNodesChange更新
例如在右侧直接修改“选中节点”的名称:
<FlowEditorReact
nodeTypes={defaultNodeTypes}
nodesValue={nodes}
connections={connections}
isRightConfig
renderRightConfig={(node, { setNodeConfig }) => {
if (!node) return null;
const label = node.label ?? "";
return (
<div style={{ padding: 8 }}>
<div style={{ marginBottom: 4 }}>节点名称:</div>
<input
style={{ width: "100%" }}
placeholder="请输入节点名称"
value={label}
onChange={(e) => {
const v = e.target.value;
setNodeConfig(node.id, "label", v);
}}
/>
</div>
);
}}
onNodesChange={setNodes}
onConnectionsChange={setConnections}
/>上面的代码会直接修改“当前选中节点”的 label 字段,画布上的节点文字会实时更新,无需手动刷新或重新挂载组件。
典型用法是:
- 在不同节点类型下渲染不同的配置表单(条件节点、审批节点、子流程节点等);
- 把复杂配置收集到
node.obj中,外部通过onNodesChange拿到完整数据; - 和你的业务后端协议对接,持久化整个流程配置。
数据结构
类型定义:src/ulits/type.ts(打包到 dist/ulits/type.d.ts)
Nodes
type Nodes = {
id: string;
x: number;
y: number;
width: number;
height: number;
type: string;
label: string;
color?: string;
src?: string;
zIndex?: number;
obj?: any;
slotName?: string;
target?: string;
groupId?: string;
};Connections
type Connections = {
id: string | number;
source: string;
target: string;
type?: string;
zIndex?: number;
isFree?: boolean;
freeSource?: { x: number; y: number };
freeTarget?: { x: number; y: number };
stroke?: string;
strokeWidth?: number;
dashed?: boolean;
arrow?: "none" | "start" | "end" | "both";
arrowStyle?: string;
animated?: boolean;
animationSpeed?: number;
hitWidth?: number;
sourceAnchor?: string;
targetAnchor?: string;
label?: string;
bendOffsetX?: number;
bendTopY?: number;
bendBottomY?: number;
cornerAngleDeg?: number;
rotationDeg?: number;
};FlowEditorReactProps 常用属性
| 属性名 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| nodesValue | Nodes[] | [] | 节点数据 |
| connections | Connections[] | [] | 连线数据 |
| backgroundType | "grid" \| "dots" \| "color" \| "none" | "grid" | 背景类型 |
| dotColor | string | "#000000" | 点状背景颜色 |
| gridColor | string | "rgba(0, 0, 0, 0.1)" | 网格背景颜色 |
| gridSize | number | 40 | 网格间距 |
| dotSpacing | number | 40 | 点状背景间距 |
| isShowNodeLabel | boolean | false | 是否显示节点上方的 Label |
| isPview | boolean | false | 是否为预览模式(不可编辑) |
你可以直接把 onNodesChange / onConnectionsChange 返回的数组序列化存数据库,后续再回填到 nodesValue / connections 即可还原画布。
常见问题
1. Invalid hook call / Hooks can only be called inside of the body of a function component
如果在你的项目里使用 cyf-react-flow 出现这个错误,通常是因为页面中存在两份 React:
- 你的项目本身有一份
react; - 另一份
react来自某个库自己的dependencies。
本库已经将 react 和 react-dom 挪到了 peerDependencies,不会再额外安装一份。如果你遇到这个问题,可以在你的项目根目录运行:
npm ls react
npm ls react-dom确保只出现一份 React 版本,如果有多份,请调整依赖或使用 resolutions / overrides 统一。
2. 样式没有生效
确认是否在入口引入了样式:
import "cyf-react-flow/index.css";如果你的打包工具对来自 node_modules 的 CSS 有特殊配置,需要保证可以正常处理第三方包的样式。
