
使用 logicflow/react-node-registry 以 React 组件自定义 LogicFlow 节点【免费下载链接】LogicFlowA flow chart editing framework focus on business customization. 专注于业务自定义的流程图编辑框架支持实现脑图、ER图、UML、工作流等各种图编辑场景。项目地址: https://gitcode.com/GitHub_Trending/lo/LogicFlowLogicFlow 是专注于业务自定义的流程图编辑框架。对于需要渲染复杂交互内容如 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: FCReactNodeProps ({ node }) { const data node.getData() if (!data.properties) data.properties {} return ( div classNamereact-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 configcomponentReact.ComponentTypeReactNodeProps渲染节点内容的 React 组件effect(keyof LogicFlow.PropertiesType)[]可选声明哪些 properties 字段变化会触发组件重渲染详见下文「精准控制更新」view...可选自定义 View默认使用包内ReactNodeViewmodel...可选自定义 Model默认使用包内ReactNodeModelregister会把type - { component, effect }存入内部维护的reactNodesMap见 registry.ts同时调用lf.register完成 LogicFlow 侧的节点注册两者通过type一一对应。ReactNodeProps组件能拿到什么组件收到的 props 类型ReactNodeProps定义在 registry.tsexport type ReactNodeProps { node: BaseNodeModel // 当前节点数据模型 graph: GraphModel // 当前画布数据模型 }node对应节点的数据模型实例可用node.getData()获取节点完整数据含 id、type、x、y、properties 等也可直接访问node.propertiesgraph整个画布的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一样当用户通过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.tsimport { 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: FCReactNodeProps ({ 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)} idgraph/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 节点进阶教程前置知识节点基础教程连接规则、锚点与边能力请参考进阶节点相关 APInodeModel、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),仅供参考