嗨,大家好,我是徐小夕。
之前一直在社区分享零代码 &低代码的技术实践,也陆陆续续设计并开发了多款可视化搭建产品,比如:
上期和大家分享了我最近做的 React-Flow
中文文档. 到今天为止, 核心部分已经完全翻译完成. 大家可以直接使用中文文档快速学习和使用 React-Flow
搭建自己的工作流.
github : https://github.com/MrXujiang/react-flow
文档地址 : http://react-flow.com
接下来我会基于我写的中文文档, 带大家做一个非常有意思的工作流案例, 方便大家快速上手 React-Flow
.
案例展示
这个案例主要包含几个技术点:
- 如何自定义节点
- 如何自定义边
- 如何设置画布缩略图和画布控件
- 如何实现嵌套节点
- 如何设置画布样式
- 如何拖拽框选多个节点
掌握了以上几点, 我们可以实现各种场景的流程图或者工作流. 上图的案例我已经推送到 github
, 大家也可以下载代码参考学习.
自定义节点
因为官方提供的节点样式比较有限,所以我们需要自定义节点和节点样式. 上述我做的案例中有三个自定义节点:
- 按钮节点
- 图片节点
- 图文标签节点(顶部根节点)
如下图所示:
具体的自定义节点的方式我在中文文档中也有详细介绍和 demo
, 这里给大家分享一下实现方式:
jsx
function LogoNode({ data, isConnectable }) {
const { src, text } = data;
return (
<div className="flow-logo">
<Handle
type="source"
position={Position.Bottom}
id="a"
// style={handleStyle}
isConnectable={isConnectable}
/>
<Handle
type="source"
position={Position.Bottom}
id="b"
isConnectable={isConnectable}
/>
<div>
<img src={src} />
<div className="flow-logo-text">{ text }</div>
</div>
</div>
);
}
做好之后我们只需要在 app
入口注册节点即可:
jsx
const nodeTypes = useMemo(() => ({ textUpdater: TextUpdaterNode }), []);
return <ReactFlow nodeTypes={nodeTypes} />;
是不是非常简单? 大家可以按照React-Flow
中文文档来学习更加复杂的自定义节点功能.
自定义边
自定义边 和自定义节点的方式类似, 我们先来看一下自定义边的案例:
大家在网上看到的花里胡哨的思维导图, 流程图的连接线, 我们其实都可以用自定义边来实现:
jsx
import {
BaseEdge,
EdgeLabelRenderer,
getStraightPath,
useReactFlow,
} from '@xyflow/react';
export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) {
const { setEdges } = useReactFlow();
const [edgePath] = getStraightPath({
sourceX,
sourceY,
targetX,
targetY,
});
return (
<>
<BaseEdge id={id} path={edgePath} />
<EdgeLabelRenderer>
<button
onClick={() => setEdges((edges) => edges.filter((e) => e.id !== id))}
>
删除
</button>
</EdgeLabelRenderer>
</>
);
}
设置画布缩略图和画布控件
一般用过figma或者设计类软件的小伙伴可能比较熟悉画布控件 和缩略图的概念.
它们可以帮助我们更高效的浏览图表和进行更便捷的图表操作. 当然 react-flow
也提供了开箱即用的插件来实现.
话不多说, 接下来我们就来看看具体的实现:
jsx
import { ReactFlow, MiniMap } from '@xyflow/react';
const defaultNodes = [
{
id: '1',
type: 'input',
data: { label: 'Dooring用户' },
position: { x: 250, y: 25 },
style: { backgroundColor: '#6ede87', color: 'white' },
},
{
id: '2',
// you can also pass a React component as a label
data: { label: <div>Dooring零代码平台</div> },
position: { x: 100, y: 125 },
style: { backgroundColor: '#ff0072', color: 'white' },
},
{
id: '3',
type: 'output',
data: { label: '发布页面' },
position: { x: 250, y: 250 },
style: { backgroundColor: '#6865A5', color: 'white' },
},
];
const defaultEdges = [
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e2-3', source: '2', target: '3', animated: true },
];
const nodeColor = (node) => {
switch (node.type) {
case 'input':
return '#6ede87';
case 'output':
return '#6865A5';
default:
return '#ff0072';
}
};
function Flow() {
return (
<div style={{ width: '100%', height: '60vh' }}>
<ReactFlow defaultNodes={defaultNodes} defaultEdges={defaultEdges} fitView>
<MiniMap nodeColor={nodeColor} nodeStrokeWidth={3} zoomable pannable />
</ReactFlow>
</div>
);
}
export default Flow;
通过上述代码我们就能实现一个非常简单的自定义缩略图的功能, 如下图所示:
如何实现嵌套节点
要想实现起嵌套节点的效果, 我们只需要调整节点结构, 即可轻松实现如下效果:
如果要将一个节点添加为另一个节点的子节点,则需要使用 parentId
(在以前的版本中称为parentNode
)选项(您可以在节点选项部分找到所有选项的列表)。一旦我们这样做了,子节点就会相对于其父节点定位。 { x: 0, y: 0 }
的位置是父级的左上角。
代码案例如下:
jsx
import { useCallback, useState } from 'react';
import {
ReactFlow,
addEdge,
applyEdgeChanges,
applyNodeChanges,
Background,
} from '@xyflow/react';
const initialNodes = [
{
id: 'A',
type: 'group',
data: { label: null },
position: { x: 0, y: 0 },
style: {
width: 170,
height: 140,
},
},
{
id: 'B',
type: 'input',
data: { label: 'Dooring Node' },
position: { x: 10, y: 10 },
parentId: 'A',
extent: 'parent',
},
{
id: 'C',
data: { label: 'React Flow' },
position: { x: 10, y: 90 },
parentId: 'A',
extent: 'parent',
},
];
const initialEdges = [
{ id: 'b-c', source: 'B', target: 'C' }
];
const rfStyle = {
backgroundColor: '#D0C0F7',
};
function Flow() {
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);
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 (
<div style={{width: '100%', height: '30vh'}}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
style={rfStyle}
attributionPosition="top-right"
>
<Background />
</ReactFlow>
</div>
);
}
export default Flow;
如何拖拽框选多个节点
如果我们更喜欢 Figma/sketch/design
工具控件,可以设置panOnScroll={true}
和selectionOnDrag={true}
:
- 平移:空格+拖动鼠标、滚动、鼠标中键或右键
- 缩放:俯仰或 cmd + 滚动
- 创建选区:拖动鼠标
这样就能实现类似多选框选的效果了:
代码如下:
jsx
import { useCallback } from 'react';
import {
ReactFlow,
addEdge,
useEdgesState,
useNodesState,
SelectionMode,
} from '@xyflow/react';
const initialNodes = [
{
id: '1',
data: { label: 'Dooring' },
position: { x: 150, y: 0 },
},
{
id: '2',
data: { label: 'Nest-Admin' },
position: { x: 0, y: 150 },
},
{
id: '3',
data: { label: 'Next-Admin' },
position: { x: 300, y: 150 },
},
];
const initialEdges = [
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e1-3', source: '1', target: '3' },
];
function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges],
);
const panOnDrag = [1, 2];
return (
<div style={{width: '100%', height: '30vh'}}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
panOnScroll
selectionOnDrag
panOnDrag={panOnDrag}
selectionMode={SelectionMode.Partial}
fitView
/>
</div>
);
}
export default Flow;
文章最开头的案例的源码我已经上传 github
了, 大家感兴趣可以学习参考一下.
后期规划
大家对于文档有好的建议, 也欢迎随时和我反馈交流~
后面会在文档中加一些笔比较复杂的可视化 + 工作流案例, 供大家学习参考.
中文文档地址: react-flow.com