使用 @logicflow/react-node-registry 以 React 组件自定义 LogicFlow 节点
【免费下载链接】LogicFlowA flow chart editing framework focus on business customization. 专注于业务自定义的流程图编辑框架,支持实现脑图、ER图、UML、工作流等各种图编辑场景。项目地址: https://gitcode.com/GitHub_Trending/lo/LogicFlow
LogicFlow 是专注于业务自定义的流程图编辑框架。对于需要渲染复杂交互内容(如 Ant Design 组件、业务表单、图表)的节点场景,官方推出了独立扩展包@logicflow/react-node-registry,允许开发者直接以 React 组件的形式注册节点内容,并自动响应节点 properties 的更新。本文将以 React 节点官方进阶教程 为主体,结合 packages/react-node-registry 的源码实现,完整讲解安装配置、register注册流程、数据驱动的节点更新、Portal 渲染模式以及标题栏等进阶能力,帮助你在实际项目中用最直观的方式完成节点自定义。
为什么需要 React 节点注册
LogicFlow 原本已经提供了通过继承HTMLNode自定义节点的能力,但从用户反馈来看,这种方式并不够直观,而且有可能因为销毁时机不对而出现性能问题。为此,LogicFlow 提供了独立的包@logicflow/react-node-registry,以一种更直观的方式来定义节点——直接传入 React 组件即可完成节点注册。
它带来两个核心收益:
- 直接复用组件库:可以立刻使用系统中已经引入的丰富组件库(如 antd、element 等),节点内容不再是手写 SVG 或原生 DOM 拼接;
- 享受 React 开发范式:利用 Hooks、Context、受控组件等 React 快捷的开发方式来定义节点内容,代码组织更符合前端团队习惯。
仓库中该包的源码位于 packages/react-node-registry/src,入口 index.ts 统一导出了view、model、registry、wrapper、portal五个模块。
环境准备与安装
@logicflow/react-node-registry的作用是把 React 组件桥接到 LogicFlow 节点系统,因此@logicflow/core、react、react-dom需要由使用方显式提供(peer 依赖),使用 npm / yarn / pnpm 均可安装:
npm install @logicflow/core @logicflow/react-node-registry react react-domyarn add @logicflow/core @logicflow/react-node-registry react react-dompnpm add @logicflow/core @logicflow/react-node-registry react react-dom安装完成后,还需要引入 LogicFlow 的基础样式(示例中通常为@logicflow/core/es/index.css),并在页面中准备一个承载图表的容器div。
注册 React 组件为节点内容
完整示例
下面是一个最基础的注册示例(与 官方示例 index.tsx 保持一致):
import React, { FC } from 'react' import LogicFlow from '@logicflow/core' import { register, ReactNodeProps } from '@logicflow/react-node-registry' // 自定义 React 组件 const NodeComponent: FC<ReactNodeProps> = ({ node }) => { const data = node.getData() if (!data.properties) data.properties = {} return ( <div className="react-algo-node"> <img src={require('@/assets/didi.png')} alt="滴滴出行" /> <span>{data.properties.name as string}</span> </div> ) } // 初始化 LogicFlow 实例 const lf = new LogicFlow({ // ...options }) // 注册自定义节点 register({ type: 'custom-react-node', component: NodeComponent, }, lf) // 渲染自定义节点 const node = lf.addNode({ id: 'react-node-1', type: 'custom-react-node', x: 80, y: 80, properties: { name: '今日出行', width: 120, height: 28, }, }) console.log('node --->>>', node)register内部会将「React 组件」与 LogicFlow 节点的view/model关联起来:默认使用包内提供的ReactNodeView(基于 HTML 节点渲染 React 内容)和ReactNodeModel(继承HtmlNodeModel的数据模型)。因此注册完成后,你就可以像使用内置节点一样,通过lf.addNode({ type: 'custom-react-node', ... })在画布上渲染它。
register 配置项解析
查看 registry.ts 中的类型定义,register接收的ReactNodeConfig包含以下字段:
| 配置项 | 类型 | 说明 |
|---|---|---|
type | string | 节点类型标识,必填;缺失时register会抛出'You should specify type in config' |
component | React.ComponentType<ReactNodeProps> | 渲染节点内容的 React 组件 |
effect | (keyof LogicFlow.PropertiesType)[] | 可选,声明哪些 properties 字段变化会触发组件重渲染(详见下文「精准控制更新」) |
view | ... | 可选,自定义 View,默认使用包内ReactNodeView |
model | ... | 可选,自定义 Model,默认使用包内ReactNodeModel |
register会把type -> { component, effect }存入内部维护的reactNodesMap(见 registry.ts),同时调用lf.register完成 LogicFlow 侧的节点注册,两者通过type一一对应。
ReactNodeProps:组件能拿到什么
组件收到的 props 类型ReactNodeProps定义在 registry.ts:
export type ReactNodeProps = { node: BaseNodeModel // 当前节点数据模型 graph: GraphModel // 当前画布数据模型 }node:对应节点的数据模型实例,可用node.getData()获取节点完整数据(含 id、type、x、y、properties 等),也可直接访问node.properties;graph:整个画布的GraphModel,可用于跨节点操作,例如graph.cloneNode(id)、graph.deleteNode(id)等。
自定义组件与内置属性
ReactNodeModel继承自HtmlNodeModel,并扩展了ReactCustomProperties(见 model.ts),因此节点的properties中支持以下内置字段:
| 属性 | 类型 | 说明 |
|---|---|---|
width/height | number | 节点尺寸,setAttributes中同步到模型 |
radius | number | 节点圆角 |
refX/refY | number | 节点文字位置偏移,通过getTextStyle生成matrixtransform 应用 |
style | CommonTheme | 节点整体样式,getNodeStyle中与默认样式合并 |
textStyle | TextNodeTheme | 节点文字样式 |
_showTitle | boolean | 是否显示标题栏 |
_title | string | 标题文字 |
_icon | string | 标题栏图标 |
_titleHeight | number | 标题栏高度,默认28 |
_expanded | boolean | 标题栏节点是否展开内容 |
需要说明的是,这些properties是「为节点框架服务」的内置约定,业务数据完全可以在此基础上自由扩展(例如示例中的name)。
properties 更新后如何同步节点内容
与HTMLNode一样,当用户通过setProperties或setProperty等方法更新节点 properties 时,react-node-registry会自动更新节点内容,无需手动触发任何刷新。
下面是在前例基础上演示动态更新的代码(注意在组件卸载时清理定时器):
const node1 = lf.addNode({ id: 'react-node-1', type: 'custom-react-node', x: 80, y: 80, properties: { name: '今日出行', width: 120, height: 28, }, }) const update = () => { node1.setProperty('name', `今日出行 ${(this.count += 1)}`) this.timer = setTimeout(update, 1000) } update() // 记得在 componentWillUnmount 中清除定时器 if (this.timer) { clearTimeout(this.timer) }底层实现:事件驱动的自动刷新
这一「自动更新」能力由 wrapper.tsx 中的Wrapper组件实现:
Wrapper在componentDidMount中监听graph.eventCenter上的NODE_PROPERTIES_CHANGE事件(wrapper.tsx);- 事件回调中通过
eventData.id === node.id判断变更是否属于当前节点; - 命中后调用
this.setState({ tick: this.state.tick + 1 })触发组件重渲染,从而驱动你的自定义组件重新执行。
使用 effect 精准控制更新
默认情况下,任何 properties 变化都会触发重渲染。如果节点内容只依赖少数几个字段,可以用effect声明「关注列表」,从而减少不必要的渲染开销。Wrapper中的判定逻辑为:
- 如果没有定义
effect,则默认更新; - 如果定义了
effect,则仅当变更的属性 key 命中effect列表时才更新(wrapper.tsx)。
register({ type: 'custom-react-node', component: NodeComponent, effect: ['name'], // 只有 name 变化时才重渲染 }, lf)注意,effect中的 key 是keyof LogicFlow.PropertiesType,即 properties 中的属性名。
Portal 方式渲染:保留 React Context
默认渲染方式及其局限
上述 React 组件的默认渲染方式,内部是通过以下方式将组件渲染到节点的 DOM 中(见 view.ts):
import { createRoot, Root } from 'react-dom/client' const root = createRoot(container) root.render(elem)可以发现,此时 React 组件已经不处于正常的渲染文档树中,组件内部无法获取外部 React Context 的内容(例如主题 Context、国际化配置、Provider 注入的数据等)。如果节点内容需要依赖这些全局 Context,就需要改用Portal模式。
Portal 模式使用示例
仓库提供了完整的 Portal 示例 Portal.tsx,核心步骤如下:
- 通过
Portal.getProvider()生成一个LFReactPortalProvider组件,并将其放置在你想要提供 Context 的 React 树中(一个 LogicFlow 实例只能声明一个 portal provider); - 正常调用
register注册节点; - 渲染节点后,组件就能通过
useContext获取到外部 Context。
import React, { FC, useContext } from 'react' import LogicFlow from '@logicflow/core' import { register, ReactNodeProps, Portal } from '@logicflow/react-node-registry' const LFReactPortalProvider = Portal.getProvider() const ThemeContext = React.createContext('light') const NodeComponent: FC<ReactNodeProps> = ({ node }) => { const theme = useContext(ThemeContext) // 此处可正常读取外部 Context const data = node.getData() if (!data.properties) data.properties = {} return ( <div className={`react-algo-node ${theme === 'light' ? 'light' : 'dark'}`}> <span>{data.properties.name as string}</span> </div> ) } export default class Example extends React.Component { private container!: HTMLDivElement componentDidMount() { const lf = new LogicFlow({ container: this.container }) lf.render({}) register({ type: 'custom-react-node', component: NodeComponent }, lf) lf.addNode({ id: 'react-node-1', type: 'custom-react-node', x: 80, y: 80, properties: { name: '今日出行', width: 120, height: 28 }, }) } render() { return ( <ThemeContext.Provider value={this.state.theme}> <LFReactPortalProvider /> <div ref={(ref) => (this.container = ref)} id="graph"></div> </ThemeContext.Provider> ) } }Portal 模式的实现机制
从 portal.ts 源码可以看到其工作原理:
Portal.getProvider()返回一个使用useReducer维护portal列表的 Provider 组件,并通过闭包将dispatch暴露给模块内部,同时把active标记置为true;- 在 view.ts 的
renderReactComponent中,会先判断Portal.isActive():如果处于激活状态,则改用createPortal(elem, container, model.id)创建ReactPortal,并通过Portal.connect(targetId, portal)将每个节点的 Portal 挂载到 Provider 的 Fragment 下; - 这样 React 组件仍然「渲染」在 Provider 的 React 树中,从而能正确消费 Context;节点卸载时则会调用
Portal.disconnect(targetId)移除对应 Portal。
节点尺寸自动测量(源码级细节)
ReactNodeView除了渲染组件外,还内置了「内容尺寸自动测量」逻辑(见 view.ts):
- 通过
ResizeObserver观察节点内容容器的首个元素(兼容环境下降级为监听window.resize); - 尺寸变化后经
requestAnimationFrame与 80ms 节流(throttle(() => this.measureAndUpdate(), 80))触发测量; - 测量得到的宽高会回写到模型:
this.props.model.setProperties({ width, height: baseHeight })(存在标题栏时会扣除_titleHeight得到内容基础高度)。
这意味着当你渲染的内容大小变化时(例如文本变长、展开收起),节点模型会自动跟随调整,无需手动维护 width/height。
标题栏能力:开箱即用的折叠与操作菜单
当节点properties._showTitle为true时,Wrapper会用 Container.tsx 包裹组件内容,并渲染 TitleBar.tsx 标题栏。相关内置配置包括:
_title:标题文字,同时作为原生title提示;_icon:标题左侧图标;_titleHeight:标题栏高度(默认 28px),且节点最小宽高会被约束为160 × 80;_expanded:内容是否展开,点击标题栏箭头按钮可切换;- 操作菜单:开启
_showTitle时,ReactNodeModel会自动注册「复制」「删除」两个动作(见 model.ts),分别调用graphModel.cloneNode与graphModel.deleteNode;也可通过setNodeActions(actions)自定义动作列表。
标题栏相关样式类(如lf-vue-node-title、lf-vue-node-container)可以在 index.less 中继续定制。
小结与进一步阅读
@logicflow/react-node-registry将 React 组件无缝桥接到 LogicFlow 节点体系:通过register({ type, component, effect })即可完成注册,properties变化由Wrapper监听NODE_PROPERTIES_CHANGE事件自动驱动更新,effect提供精细化的渲染控制;当节点需要读取外部 React Context 时,可用Portal模式渲染;ResizeObserver与标题栏能力则进一步提升了开箱即用的体验。
- 阅读本文对应官方文档:React 节点进阶教程;
- 前置知识:节点基础教程;连接规则、锚点与边能力请参考进阶节点;
- 相关 API:nodeModel、graphModel、事件;
- 源码与示例:react-node-registry 源码、基础示例 index.tsx、Portal 示例 Portal.tsx。
【免费下载链接】LogicFlowA flow chart editing framework focus on business customization. 专注于业务自定义的流程图编辑框架,支持实现脑图、ER图、UML、工作流等各种图编辑场景。项目地址: https://gitcode.com/GitHub_Trending/lo/LogicFlow
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考