使用 @logicflow/react-node-registry 以 React 组件自定义 LogicFlow 节点
2026/9/15 16:30:45 网站建设 项目流程

使用 @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 统一导出了viewmodelregistrywrapperportal五个模块。

环境准备与安装

@logicflow/react-node-registry的作用是把 React 组件桥接到 LogicFlow 节点系统,因此@logicflow/corereactreact-dom需要由使用方显式提供(peer 依赖),使用 npm / yarn / pnpm 均可安装:

npm install @logicflow/core @logicflow/react-node-registry react react-dom
yarn add @logicflow/core @logicflow/react-node-registry react react-dom
pnpm 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包含以下字段:

配置项类型说明
typestring节点类型标识,必填;缺失时register会抛出'You should specify type in config'
componentReact.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/heightnumber节点尺寸,setAttributes中同步到模型
radiusnumber节点圆角
refX/refYnumber节点文字位置偏移,通过getTextStyle生成matrixtransform 应用
styleCommonTheme节点整体样式,getNodeStyle中与默认样式合并
textStyleTextNodeTheme节点文字样式
_showTitleboolean是否显示标题栏
_titlestring标题文字
_iconstring标题栏图标
_titleHeightnumber标题栏高度,默认28
_expandedboolean标题栏节点是否展开内容

需要说明的是,这些properties是「为节点框架服务」的内置约定,业务数据完全可以在此基础上自由扩展(例如示例中的name)。

properties 更新后如何同步节点内容

HTMLNode一样,当用户通过setPropertiessetProperty等方法更新节点 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组件实现:

  1. WrappercomponentDidMount中监听graph.eventCenter上的NODE_PROPERTIES_CHANGE事件(wrapper.tsx);
  2. 事件回调中通过eventData.id === node.id判断变更是否属于当前节点;
  3. 命中后调用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,核心步骤如下:

  1. 通过Portal.getProvider()生成一个LFReactPortalProvider组件,并将其放置在你想要提供 Context 的 React 树中(一个 LogicFlow 实例只能声明一个 portal provider);
  2. 正常调用register注册节点;
  3. 渲染节点后,组件就能通过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._showTitletrue时,Wrapper会用 Container.tsx 包裹组件内容,并渲染 TitleBar.tsx 标题栏。相关内置配置包括:

  • _title:标题文字,同时作为原生title提示;
  • _icon:标题左侧图标;
  • _titleHeight:标题栏高度(默认 28px),且节点最小宽高会被约束为160 × 80
  • _expanded:内容是否展开,点击标题栏箭头按钮可切换;
  • 操作菜单:开启_showTitle时,ReactNodeModel会自动注册「复制」「删除」两个动作(见 model.ts),分别调用graphModel.cloneNodegraphModel.deleteNode;也可通过setNodeActions(actions)自定义动作列表。

标题栏相关样式类(如lf-vue-node-titlelf-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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询