嗨,大家好,我是徐小夕。
之前一直在社区分享零代码&低代码的技术实践,也陆陆续续设计并开发了多款可视化搭建产品,比如:
H5-Dooring(页面可视化搭建平台)V6.Dooring(可视化大屏搭建平台)橙子试卷(表单搭建引擎)Nocode/WEP 文档知识引擎上期和大家分享了我最近做的 React-Flow 中文文档. 到今天为止, 核心部分已经完全翻译完成. 大家可以直接使用中文文档快速学习和使用 React-Flow 搭建自己的工作流.
github: https://github.com/MrXujiang/react-flow
文档地址: http://react-flow.com
接下来我会基于我写的中文文档, 带大家做一个非常有意思的工作流案例, 方便大家快速上手 React-Flow.徐小夕【知乎专栏作家】掘金签约作者,定期分享前端工程化,可视化,企业实战项目知识,深度复盘企业中经常遇到的500+技术问题解决方案。【关注趣谈前端,前端路上不迷茫】
案例展示
这个案例主要包含几个技术点:
如何自定义节点如何自定义边如何设置画布缩略图和画布控件如何实现嵌套节点如何设置画布样式如何拖拽框选多个节点掌握了以上几点, 我们可以实现各种场景的流程图或者工作流. 上图的案例我已经推送到 github, 大家也可以下载代码参考学习.
自定义节点因为官方提供的节点样式比较有限,所以我们需要自定义节点和节点样式. 上述我做的案例中有三个自定义节点:
按钮节点图片节点图文标签节点(顶部根节点)如下图所示:
具体的自定义节点的方式我在中文文档中也有详细介绍和 demo, 这里给大家分享一下实现方式:
function LogoNode({ data, isConnectable }) { const { src, text } = data; return ( <divName="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} /> <divName="flow-logo-text">{ text }</div> </div> </div> ); }做好之后我们只需要在 app 入口注册节点即可:
const nodeTypes = useMemo(() => ({ textUpdater: TextUpdaterNode }), []); return <ReactFlow nodeTypes={nodeTypes} />;是不是非常简单? 大家可以按照React-Flow 中文文档来学习更加复杂的自定义节点功能.
自定义边自定义边和自定义节点的方式类似, 我们先来看一下自定义边的案例:
大家在网上看到的花里胡哨的思维导图, 流程图的连接线, 我们其实都可以用自定义边来实现:
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 也提供了开箱即用的插件来实现.
话不多说, 接下来我们就来看看具体的实现:
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 } 的位置是父级的左上角。
代码案例如下:
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 + 滚动创建选区:拖动鼠标这样就能实现类似多选框选的效果了:
代码如下:
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 了, 大家感兴趣可以学习参考一下. 完整案例效果图:
后期规划
大家对于文档有好的建议, 也欢迎随时和我反馈交流~
后面会在文档中加一些笔比较复杂的可视化 + 工作流案例, 供大家学习参考.
https://github.com/MrXujiang/react-flow
中文文档地址: http://react-flow.com
后续我也会持续迭代 H5-Dooring 零代码项目,让它成为最好用的可视化 + 无代码应用搭建工具,如果大家感兴趣,也随时欢迎留言区反馈交流~