Article

工作流 Rete.js

更新于:2026-07-10

第1章:Rete.js 简介与核心概念

1.1 什么是 Rete.js

概念名称说明注意事项
Rete.js一个基于 JavaScript 的可视化节点编辑器框架,用于构建可交互的流程图和规则引擎。它允许开发者通过拖拽节点和连接线的方式设计复杂的逻辑流程。Rete.js 不是完整的低代码平台,而是一个可扩展的底层框架,需结合插件和自定义逻辑使用。
开源协议MIT 许可证,允许自由使用、修改和分发,适用于商业项目。使用时建议查看最新版本的 LICENSE 文件以确认条款。
核心目标提供一套模块化、可扩展的 API,用于创建基于 Rete 算法的运行时节点网络。主要关注逻辑执行层,UI 渲染需依赖额外的渲染插件(如 Vue 或 React 渲染器)。
框架定位属于”节点编辑器引擎”,与 Node-RED、LogicFlow、Drawflow 类似,但更侧重于规则推理和高效匹配。不直接提供图形界面组件,需集成 render plugin 实现可视化。

1.2 Rete 算法原理简介

概念名称说明注意事项
Rete 算法起源由 Charles L. Forgy 在 1979 年提出,用于高效匹配规则条件与事实数据。原始算法用于专家系统和规则引擎(如 CLIPS、Drools),Rete.js 是其 JS 实现变体。
匹配-选择-执行循环(Match-Select-Act)规则引擎的基本运行机制:
1. 匹配:将规则条件与当前工作内存中的事实进行比对;
2. 选择:从满足条件的规则中选择一条执行;
3. 执行:运行选中规则的动作。
Rete.js 默认不强制实现选择策略,开发者可自定义执行顺序。
工作内存(Working Memory)存储当前所有”事实”(facts)的地方,通常是输入数据或中间结果。在 Rete.js 中,工作内存由 Engine 和节点间的数据传递模拟实现。
生产规则(Production Rules)形如”if 条件 then 动作”的逻辑表达式。每个节点可视为一个规则单元。Rete.js 将规则拆分为节点(Node)和连接(Connection),形成有向图结构。
网络结构(Rete Network)一种有向无环图(DAG),节点表示条件或操作,边表示数据流或控制流。Rete.js 构建的节点图本质上是对 Rete 网络的简化和泛化,支持动态修改。
节点类型(常见)- Alpha 节点:过滤单个条件
- Beta 节点:组合多个条件
- 终端节点:触发动作
Rete.js 抽象了这些类型,开发者通过自定义 Node 类实现对应逻辑。
增量更新机制当事实变化时,仅重新计算受影响的路径,避免全量匹配,提升性能。Rete.js 利用连接依赖关系实现类似机制,适合频繁更新的场景。

1.3 Rete.js 的架构与组件概览

组件名称说明注意事项
Editor(编辑器实例)核心容器,管理节点、连接、事件和插件的生命周期。必须首先创建 Editor 实例,其他组件依附其上。
Node(节点)表示一个处理单元,包含输入、输出、控制项和执行逻辑。需继承 Rete.Node 或使用 nodeCreator 函数创建自定义节点。
Input / Output节点的输入端口和输出端口,定义数据流入流出的通道。类型需匹配才能连接,支持数据类型校验(如 number、string、any)。
Connection(连接)表示两个端口之间的连线,传递数据或触发执行。由 ConnectionPlugin 管理,不可跨类型连接(可通过 any 类型绕过)。
Control(控件)嵌入节点内部的 UI 元素,如文本框、按钮等,用于配置节点参数。不参与数据流,仅用于用户交互,需配合渲染器使用。
Engine(执行引擎)负责从输入节点开始,按连接顺序执行节点逻辑,支持同步/异步执行。必须注册所有节点类型,否则无法执行;可通过 engine.execute() 触发运行。
Plugin(插件系统)模块化扩展机制,如渲染、连接校验、上下文菜单等均由插件提供。插件需按顺序注册,部分插件有依赖关系(如 RenderPlugin 依赖 ConnectionPlugin)。

1.4 典型应用场景分析

应用场景说明注意事项
可视化编程工具如 Blockly、Node-RED 替代方案,允许用户通过图形化方式编写程序逻辑。适合教育、自动化脚本生成等场景,需设计丰富的节点库。
业务规则引擎实现”如果订单金额 > 1000,则打 9 折”类逻辑,便于非技术人员配置规则。结合后端持久化和版本管理,提升可维护性。
游戏对话系统构建 NPC 对话树,支持分支、条件判断和剧情跳转。可将每个对话节点作为 Rete 节点,通过条件输出控制流程走向。
数据处理流水线定义 ETL(抽取、转换、加载)流程,如清洗数据 → 聚合 → 输出报表。每个处理步骤为一个节点,支持并行和串行执行。
AI 行为树(Behavior Tree)构建游戏角色的决策逻辑,如”巡逻 → 发现敌人 → 追击”。需扩展节点类型以支持并行、选择、重复等行为树结构。
低代码/无代码平台作为底层引擎支撑可视化逻辑编排功能,集成到更大系统中。需配合表单设计器、API 管理等模块使用,注重用户体验和稳定性。
自动化测试流程编排定义测试用例的执行顺序、条件分支和断言逻辑。支持动态参数注入和结果反馈,便于调试和复用。

第2章:环境搭建与第一个节点编辑器

2.1 安装 Rete.js 及依赖

方法名称语法用途代码示例注意事项
npm installnpm install rete安装 Rete.js 核心库npm install rete必须首先安装核心库,否则无法创建编辑器实例。
yarn addyarn add rete使用 Yarn 包管理器安装 Rete.jsyarn add rete功能与 npm 相同,适用于使用 Yarn 的项目。
安装渲染插件npm install rete-vue-render-plugin@next安装 Vue 3 渲染插件(推荐用于 Vue 项目)npm install rete-vue-render-plugin@next若使用 Vue,必须安装对应版本的渲染插件;@next 表示最新实验版。
安装渲染插件npm install rete-react-render-plugin安装 React 渲染插件(用于 React 项目)npm install rete-react-render-pluginReact 渲染插件支持函数组件和类组件集成。
安装连接插件npm install rete-connection-plugin安装连接管理插件,支持节点间的连线操作npm install rete-connection-plugin所有可视化编辑功能都需要此插件,否则无法创建连接。
安装控制插件npm install rete-control-plugin安装控制项插件,用于在节点中添加按钮、输入框等 UI 控件npm install rete-control-plugin非必需,但常用于配置节点参数。

2.2 创建基础编辑器实例

方法名称语法用途代码示例注意事项
Editor 构造函数new Editor()创建一个空的节点编辑器实例import { NodeEditor } from 'rete';
const editor = new NodeEditor();
必须先创建 editor 实例,后续所有组件都注册到该实例上。
register()editor.register(nodeClass)注册自定义节点类型,使编辑器识别该节点editor.register(MyNode);每个自定义节点类必须通过 register 注册后才能使用。
connect()editor.connect(output, input)在两个端口之间建立连接await editor.connect(outPort, inPort);返回 Promise,需 await 等待连接完成;连接失败会抛出错误。
nodeseditor.nodes获取当前编辑器中所有节点的数组const allNodes = editor.nodes;只读属性,返回节点实例列表,可用于遍历或调试。
vieweditor.view获取编辑器的视图对象(由渲染插件提供)const { container } = editor.view;仅在渲染插件加载后可用,用于将编辑器挂载到 DOM 容器。
addNode()editor.addNode(nodeInstance)向编辑器添加一个节点实例await editor.addNode(new MyNode());异步方法,需 await;节点必须已注册且构造完成。
removeNode()editor.removeNode(nodeInstance)从编辑器中移除指定节点await editor.removeNode(node);同时会自动断开相关连接。
on()editor.on(eventName, callback)监听编辑器事件,如 'process', 'nodecreate'editor.on('nodecreate', (node) => console.log(node));支持多个事件类型,可用于调试或触发外部逻辑。
trigger()editor.trigger(eventData)手动触发一个事件editor.trigger({ type: 'customEvent', data: {} });自定义事件可用于跨模块通信。

2.3 使用 Vue 或 React 集成 Rete.js(可选框架)

Vue 集成

方法名称语法用途代码示例注意事项
VueRenderPluginimport { VueRenderPlugin } from 'rete-vue-render-plugin';
editor.use(VueRenderPlugin);
将 Vue 渲染能力注入编辑器const render = VueRenderPlugin();
editor.use(render);
必须在调用 mount 前 use 插件;支持 Vue 3 Composition API。
render.add(nodeType, component)render.add(MyNode, MyNodeComponent);为特定节点类型绑定 Vue 组件// MyNodeComponent 是一个 defineComponent 定义的组件组件需暴露 props 接收节点数据,并可通过 emit 与逻辑层通信。
mount()render.mount(container, editor);将编辑器渲染到指定 DOM 容器const container = document.getElementById('editor');
render.mount(container, editor);
container 必须存在且可见;建议在 Vue 的 onMounted 钩子中调用。

React 集成

方法名称语法用途代码示例注意事项
ReactRenderPluginimport { ReactRenderPlugin } from 'rete-react-render-plugin';
editor.use(ReactRenderPlugin);
将 React 渲染能力注入编辑器const render = ReactRenderPlugin();
editor.use(render);
支持 React 16+,可在函数组件中使用。
render.add(nodeType, component)render.add(MyNode, MyNodeComponent);为特定节点类型绑定 React 函数组件// MyNodeComponent 是一个 (props) => JSX.Element 形式的组件props 包含 node、emit 等属性,可用于响应用户交互。
render.render()render.render(editor, container);渲染编辑器到指定 DOM 节点render.render(editor, document.getElementById('editor'));需确保 container 已挂载到 DOM;通常在 useEffect 中执行。
render.destroy()render.destroy();销毁渲染实例,清理事件监听和 DOM 元素useEffect(() => { return () => render.destroy(); }, []);在组件卸载时调用,防止内存泄漏。

第3章:核心模块详解

3.1 Engine 模块:节点执行与控制流

方法名称语法用途代码示例注意事项
Engine 构造函数new Engine(processors)创建一个执行引擎实例,可传入处理器映射表import { Engine } from 'rete';
const engine = new Engine();
必须创建 Engine 实例来执行节点逻辑,通常与编辑器配对使用。
register()engine.register(nodeClass)注册节点类,使引擎知道如何执行该类型节点engine.register(MyNode);所有参与执行的节点类型都必须注册,否则 execute 会跳过。
process()engine.process(data)向引擎输入初始数据,触发从输入节点开始的执行流程await engine.process({ input: 10 });异步方法,返回 Promise;可用于传递全局上下文或触发信号。
execute()engine.execute(fromNode, input, data)从指定节点的输入端口开始执行,传递数据await engine.execute(node, 'exec', value);用于控制流触发;fromNode 为节点实例,input 为输入名,data 为传递值。
async execute()engine.execute(node, 'data', value)异步执行支持,节点可返回 Promiseasync execute(input, data) { return data * 2; }支持 await 处理异步逻辑,如 API 调用、延时等。
remove()engine.remove(nodeClass)从引擎中移除某类节点的注册engine.remove(MyNode);移除后该类型节点将不再被执行。
processorsengine.processors获取当前注册的所有处理器(节点执行逻辑)const processors = engine.processors;只读属性,可用于调试或动态修改执行逻辑。

3.2 Node 模块:自定义节点类

方法名称语法用途代码示例注意事项
Node 构造函数class MyNode extends Rete.Node { }定义一个自定义节点类class AddNode extends Rete.Node { constructor() { super('Add'); } }必须继承 Rete.Node,构造函数中调用 super 并传入节点名称。
constructor()constructor() { super(name); }初始化节点,设置节点名称super('CustomNode');name 将显示在编辑器中,建议唯一且语义化。
builder()async builder(node)构建节点结构:添加输入、输出、控制项builder(node) { node.addInput(new Input('num', 'Number')); }异步方法,可动态加载配置;所有端口和控件在此方法中定义。
worker()async worker(node, inputs, outputs)节点执行时调用的工作函数,处理数据逻辑worker(node, inputs) { return inputs['num'] * 2; }必须定义,否则节点无执行逻辑;inputs 是输入数据对象,outputs 是输出对象。
idnode.id节点实例的唯一标识符const nodeId = node.id;由编辑器自动生成,全局唯一,可用于序列化或调试。
namenode.name节点类型名称(来自构造函数)const typeName = node.name;用于区分不同类型的节点。
addInput()node.addInput(inputInstance)向节点添加一个输入端口this.addInput(new Rete.Input('value', 'Value'));必须在 builder 中调用。
addOutput()node.addOutput(outputInstance)向节点添加一个输出端口this.addOutput(new Rete.Output('result', 'Result'));支持多个输出,用于分支逻辑。
addControl()node.addControl(controlInstance)向节点添加一个 UI 控件this.addControl(new Rete.Control('text'));控件不参与数据流,仅用于用户配置。

3.3 Input / Output 模块:数据输入输出定义

方法名称语法用途代码示例注意事项
Input 构造函数new Input(key, name, socket, multiple)创建一个输入端口new Input('in', 'Input', socket, false);key 用于数据访问,name 显示在 UI,socket 定义类型,multiple 是否允许多连接。
Output 构造函数new Output(key, name, socket, multiple)创建一个输出端口new Output('out', 'Output', socket, true);multiple=true 时可连接多个下游节点。
keyinput.key / output.key端口的唯一键名,用于 worker 中访问数据inputs['value'] // 使用 key 获取输入值必须唯一且符合变量命名规范。
nameinput.name / output.name显示在 UI 上的端口标签new Input('val', 'Value', socket); // UI 显示 "Value"可本地化,不影响逻辑。
socketinput.socket / output.socket定义端口的数据类型和连接规则const numSocket = new Rete.Socket('Number');
new Input('n', 'Num', numSocket);
相同 socket 类型的端口才能连接。
multipleinput.multiple / output.multiple是否允许多个连接new Output('data', 'Data', socket, true);multiple=false 时新连接会替换旧连接。
isInputinput.isInput判断是否为输入端口(只读)if (port.isInput) { ... }用于通用逻辑处理。
isOutputoutput.isOutput判断是否为输出端口(只读)if (port.isOutput) { ... }用于遍历和校验。

3.4 Connection 模块:连接逻辑与校验

方法名称语法用途代码示例注意事项
Connection 构造函数new Connection(from, output, to, input)创建两个节点端口之间的连接new Connection(node1, 'out', node2, 'in');from 和 to 为节点实例,output 和 input 为端口 key。
fromconnection.from源节点实例const sourceNode = connection.from;只读属性。
toconnection.to目标节点实例const targetNode = connection.to;只读属性。
outputconnection.output源节点的输出端口 keyconst outKey = connection.output;用于定位数据来源。
inputconnection.input目标节点的输入端口 keyconst inKey = connection.input;用于定位数据去向。
connect()editor.connect(from, output, to, input)通过编辑器创建连接(推荐方式)await editor.connect(node1, 'out', node2, 'in');返回 Promise,自动触发渲染更新。
disconnect()editor.removeConnection(connection)移除指定连接await editor.removeConnection(conn);同时从编辑器中删除该连接实例。
validatorconnection.validator = (connection) => {}自定义连接校验函数,决定是否允许连接conn.validator = (c) => c.from.name !== c.to.name;返回 true 允许,false 禁止;可在 builder 或插件中设置。

3.5 Control 模块:UI 控件集成(如按钮、文本框)

方法名称语法用途代码示例注意事项
Control 构造函数new Control(component, props, emitters)创建一个嵌入节点的 UI 控件new Control('input', { value: 'hello' });component 可为字符串(内置)或组件对象,props 传递初始值。
addComponent()control.addComponent(name, component)注册一个可复用的控件组件Control.addComponent('my-button', MyButton);全局注册,可在多个节点中使用。
setValue()control.setValue(value)更新控件的值,并触发 change 事件control.setValue(42);通常用于外部逻辑更新控件状态。
getValue()control.getValue()获取控件当前值const val = control.getValue();用于在 worker 中读取用户配置。
emit()control.emit(event)触发控件事件,通知节点或其他模块control.emit('click');可自定义事件类型,常用于按钮点击等交互。
componentcontrol.component获取绑定的 UI 组件const comp = control.component;可用于动态切换控件类型。
propscontrol.props获取传递给控件的属性const { value } = control.props;只读,初始化时设置。
emitterscontrol.emitters获取事件发射器映射表control.emitters['change'] = (val) => {...};用于监听控件内部事件,如输入框 change。

第4章:视图层与渲染器(Renderer)

4.1 使用 Rete.js 默认渲染器(VueRenderPlugin / ReactRenderPlugin)

方法名称语法用途代码示例注意事项
VueRenderPluginimport { VueRenderPlugin } from 'rete-vue-render-plugin';创建 Vue 渲染插件实例editor.use(VueRenderPlugin());必须在 editor.use() 中注册,且在 mount 前完成。
const render = VueRenderPlugin();
ReactRenderPluginimport { ReactRenderPlugin } from 'rete-react-render-plugin';创建 React 渲染插件实例editor.use(ReactRenderPlugin());支持 React 16+,需确保 React 和 ReactDOM 已安装。
const render = ReactRenderPlugin();
render.add()render.add(NodeClass, Component)为指定节点类型绑定自定义 UI 组件render.add(NumberNode, NumberNodeView);Component 为 Vue 或 React 组件,接收 node、emit 等 props。
render.mount()render.mount(container, editor)将编辑器挂载到 DOM 容器(Vue)const container = document.getElementById('editor');
render.mount(container, editor);
container 必须存在;Vue 项目中通常在 onMounted 钩子调用。
render.render()render.render(editor, container)渲染编辑器到指定容器(React)render.render(editor, document.getElementById('editor'));React 项目使用此方法,替代 mount。
render.destroy()render.destroy()销毁渲染实例,清理事件和 DOMrender.destroy();组件卸载时必须调用,防止内存泄漏。
use()editor.use(plugin, options)将渲染插件注册到编辑器editor.use(VueRenderPlugin({ customize: {} }));options 可用于定制渲染行为。

4.2 自定义节点 UI 样式与布局

概念/方法名称说明代码示例注意事项
节点 CSS 类名渲染器为节点容器添加默认类名 .node,类型名如 .number-node.node { background: #fff; border: 1px solid #ccc; }
.add-node { width: 120px; }
可通过全局 CSS 定制样式,建议使用 BEM 命名避免冲突。
端口类名输入端口:.input,输出端口:.output,控制项:.control.input { color: blue; }
.output .port { background: green; }
端口元素可通过类名精确定位,用于调整位置或颜色。
自定义组件结构在 Vue/React 组件中完全控制节点 HTML 结构Vue: 模板语法 + defineComponent
React: JSX + 函数组件
可实现复杂布局,如多行文本、图标、状态指示灯等。
动态类名绑定根据节点状态动态添加 CSS 类:class="{ 'active': node.selected }"用于高亮选中节点、错误状态提示等交互反馈。
内联样式使用 style 属性直接设置样式style={{ width: '120px' }}适用于动态尺寸或位置调整,但优先级高于外部 CSS。
布局建议推荐使用 Flexbox 或 Grid 布局确保端口对齐display: flex; justify-content: space-between;避免绝对定位导致响应式问题。
响应式设计使用相对单位(em, rem, %)和媒体查询@media (max-width: 768px) { .node { font-size: 12px; } }适配移动端或小屏幕设备。

4.3 事件绑定与用户交互处理

方法名称语法用途代码示例注意事项
emit()control.emit('event', data)从控件触发事件,通知节点或引擎control.emit('update', newValue);常用于输入框 change、按钮 click 等场景。
on()editor.on('nodeselected', callback)监听编辑器级事件editor.on('nodeselected', ({ node }) => console.log(node));支持事件:'nodeselected', 'nodedeselected', 'connectioncreated' 等。
node.selectednode.selected判断节点是否被选中if (node.selected) { ... }可用于 UI 高亮或条件操作。
handle click@click="() => emit('click')"在 Vue 组件中绑定点击事件<button @click="() => emit('click')">执行</button>emit 第二个参数可传递数据。
handle input change@input="e => emit('input', e.target.value)"监听输入框变化并发送值<input @input="e => emit('input', e.target.value)" />推荐使用 input 事件而非 change,响应更及时。
drag start / endeditor.on('dragstart', handler)监听拖拽节点开始和结束editor.on('dragstart', () => lockOtherUI());
editor.on('dragend', unlockUI);
可用于禁用其他操作避免冲突。
contextmenueditor.on('contextmenu', handler)处理右键菜单事件editor.on('contextmenu', (e) => showMenu(e.x, e.y));需阻止默认行为 e.preventDefault()
doubleClickeditor.on('nodedoubleclicked', handler)监听节点双击事件editor.on('nodedoubleclicked', ({ node }) => openConfigPanel(node));适合打开配置弹窗或进入编辑模式。

第5章:数据流与执行引擎

5.1 数据流动机制(Dataflow)

概念/机制名称说明代码示例或逻辑描述注意事项
推模式 (Push)数据由上游节点输出后,主动”推送”给下游节点触发执行当节点 A 输出数据,连接的节点 B 自动调用其 worker 方法Rete.js 默认采用推模式,适合事件驱动和实时响应场景。
拉模式 (Pull)下游节点在需要时主动”拉取”上游节点的数据节点 B 在 worker 中显式调用 getInputData('inputName') 获取数据需自定义实现,适合按需计算或性能优化场景。
数据触发连接建立后,上游节点的输出数据会自动触发下游节点的输入连接 A.out -> B.in 后,A 执行完输出数据,B 立即开始执行触发依赖于连接和端口类型匹配。
数据缓存节点可缓存上一次的输出结果,避免重复计算node.cache = true;在 worker 中可通过 this.cache 访问缓存,优化性能。
端口键(key)每个输入/输出端口有唯一 key,用于数据传递和 worker 中访问inputs['value'] 获取 key 为 'value' 的输入数据key 必须与连接时定义的 key 一致。
Socket 类型匹配只有相同或兼容 Socket 类型的端口才能连接,保证数据类型安全new Socket('Number') 的输出只能连接到同类型输入是数据流校验的核心机制,防止类型错误。
多输入合并一个节点可有多个输入,数据以对象形式传入 workerworker(node, inputs) { const a = inputs['in1']; const b = inputs['in2']; }输入数据按 key 组织,未连接的输入为 null 或 undefined。

5.2 使用 Engine 执行节点逻辑

方法/属性名称语法用途代码示例注意事项
Engine 实例化const engine = new Engine();创建执行引擎import { Engine } from 'rete';
const engine = new Engine();
必须先创建引擎,并与编辑器关联。
register()engine.register(NodeClass)注册节点类,使引擎知道如何执行该节点engine.register(AddNode);所有参与执行的节点类型都必须注册,否则 execute 会跳过。
process()await engine.process(data)启动执行流程,向引擎注入初始数据await engine.process({ start: true });异步方法,常用于触发整个流程或传递全局参数。
execute()await engine.execute(node, input, data)从指定节点的指定输入端口开始执行,传递数据await engine.execute(node, 'exec', 100);node 是节点实例,input 是输入端口 key,data 是传递的值。
remove()engine.remove(NodeClass)从引擎中移除某类节点的注册engine.remove(CustomNode);移除后该类型节点不再被执行。
processorsengine.processors获取当前注册的所有处理器(节点执行逻辑)console.log(engine.processors);只读属性,可用于调试或动态管理。
worker 执行流程async worker(node, inputs, outputs)节点的工作函数,处理输入、执行逻辑、返回输出worker(node, inputs) { return { result: inputs['num'] * 2 }; }必须返回一个对象,键为输出端口 key,值为输出数据。
错误处理try { await engine.process() } catch(e)捕获执行过程中的异常try { await engine.process(); } catch (err) { console.error(err); }节点 worker 抛出异常会中断执行流,需妥善处理。

5.3 异步节点支持与 Promise 处理

特性/方法名称说明代码示例注意事项
async workerworker 方法支持 async,可返回 Promiseasync worker(node, inputs) {
  const res = await fetch('/api/data');
  return { result: await res.json() };
}
引擎会自动 await worker 的返回值,支持异步操作。
Promise 返回worker 可直接返回 Promise 对象worker() { return Promise.resolve({ result: 42 }); }与 async/await 效果相同,提供更灵活的异步控制。
并行执行多个异步节点可并行执行,互不阻塞节点 A 和 B 无依赖关系时,engine 会同时触发其 worker提高执行效率,特别适合 I/O 密集型任务。
顺序执行通过连接控制执行顺序,前一个异步节点完成后再触发下一个A.out -> B.in,B 在 A 的 worker 完成后才开始执行保证数据依赖的正确性。
错误传播异步 worker 中 reject 或抛出异常,会中断后续执行流async worker() { throw new Error('API failed'); }需在 process() 或 execute() 外层 try/catch 捕获。
超时处理可结合 Promise.race()AbortController 实现超时const timeout = new Promise((_, r) => setTimeout(() => r('timeout'), 5000));
return Promise.race([fetchCall, timeout]);
防止异步操作无限等待,提升系统健壮性。
状态反馈异步执行期间,可通过 control.emit() 更新 UI 状态(如加载中)control.emit('loading', true);
const data = await apiCall();
control.emit('loading', false);
提升用户体验,让用户知晓执行进度。

第6章:高级功能扩展

6.1 插件系统介绍(Plugin System)

Rete.js 采用模块化插件架构,允许功能解耦和按需扩展。插件通过 editor.use(plugin) 注册,可访问编辑器实例并扩展其功能。

概念/机制名称说明代码示例或逻辑描述注意事项
插件定义插件是一个对象或函数,包含 name、install 方法或直接为 install 函数function MyPlugin() { return { name: 'MyPlugin', install: (editor) => { ... } }; }推荐使用对象形式,便于调试和管理。
install 方法插件注册时自动调用,接收 editor 实例作为参数install(editor) { editor.on('process', () => console.log('Processing...')); }可在此方法中监听事件、扩展 API、注册子插件。
editor.use()注册插件的核心方法editor.use(MyPlugin());必须在编辑器初始化后、挂载前调用。
插件依赖插件可声明对其他插件的依赖,确保加载顺序在 install 中检查所需 API 是否存在例如:ConnectionPlugin 必须在 VueRenderPlugin 前注册。
插件通信插件间通过 editor.emit()editor.on() 事件机制通信editor.emit('custom-event', data);
editor.on('custom-event', handler);
避免直接访问其他插件内部状态,保持松耦合。
生命周期管理插件应提供 destroy 方法清理资源(如事件监听)install(editor) { const handler = () => {}; editor.on('event', handler); return () => editor.off('event', handler); }防止内存泄漏,组件卸载时调用。

6.2 常用插件详解

插件名称用途安装与使用示例核心功能与注意事项
ConnectionPlugin提供节点间连接的创建、删除、校验等核心功能import { ConnectionPlugin } from 'rete-connection-plugin';
editor.use(ConnectionPlugin());
- 必须插件,无它则无法连接节点。
- 支持自定义连接样式和校验逻辑。
ContextMenuPlugin为编辑器添加右键上下文菜单,支持自定义菜单项import { ContextMenuPlugin } from 'rete-context-menu-plugin';
editor.use(ContextMenuPlugin({ items: [...] }));
- items 数组定义菜单项(文本、点击回调)。
- 支持动态生成菜单。
MinimapPlugin添加小地图视图,便于在大型画布中导航import { MinimapPlugin } from 'rete-minimap-plugin';
editor.use(MinimapPlugin());
- 提供缩略图和视口框。
- 可拖拽视口框快速定位。
- 占用额外 DOM。
AreaPlugin管理画布区域(缩放、平移、拖拽)import { AreaPlugin } from 'rete-area-plugin';
const area = AreaPlugin();
editor.use(area);
- 提供 zoom, translate 方法。
- 处理鼠标/触摸事件。
AutoArrangePlugin自动排列节点,优化布局import { AutoArrangePlugin } from 'rete-auto-arrange-plugin';
editor.use(AutoArrangePlugin());
- 基于 DAG(有向无环图)算法布局。
- 可通过快捷键触发。
Vue/Konva RenderPlugin分别提供 Vue/React 或 Canvas 渲染能力import { VueRenderPlugin } from 'rete-vue-render-plugin';
editor.use(VueRenderPlugin());
- Vue/React:组件化 UI,易集成。
- Konva:高性能,适合复杂图形。
InspectorPlugin提供节点属性检查器面板,集中编辑选中节点的控件import { InspectorPlugin } from 'rete-inspector-plugin';
editor.use(InspectorPlugin());
- 类似 Unity Inspector。
- 提升大批量配置效率。

6.3 自定义插件开发

开发步骤说明代码示例注意事项
1. 定义插件结构创建函数或对象,返回包含 install 方法的对象function MyCustomPlugin(options = {}) {
  return {
    name: 'MyCustomPlugin',
    install: (editor) => {
      // 扩展逻辑
    }
  };
}
name 用于调试和去重。
2. 监听编辑器事件在 install 中使用 editor.on() 监听关键事件editor.on('nodeselected', ({ node }) => {
  console.log('Selected:', node);
});
可监听:'nodecreated', 'noderemoved', 'connectioncreated' 等。
3. 扩展编辑器 API向 editor 添加自定义方法editor.exportToJson = () => {
  return { nodes: editor.nodes.toArray() };
};
方法可被其他插件或业务代码调用。
4. 集成 UI 组件结合渲染插件,在 DOM 中添加自定义 UI(如按钮、面板)const button = document.createElement('button');
button.innerText = 'Export';
button.onclick = () => editor.emit('export');
container.appendChild(button);
注意挂载和销毁时机,避免内存泄漏。
5. 提供配置选项通过 options 参数定制插件行为install(editor) {
  const { autoSaveInterval = 5000 } = options;
  setInterval(() => editor.emit('autosave'), autoSaveInterval);
}
增强插件灵活性和复用性。
6. 销毁资源返回一个清理函数,由编辑器在销毁时调用let timer;
timer = setInterval(...);
return () => clearInterval(timer);
确保所有事件监听、定时器、DOM 元素被正确清理。
7. 发布事件使用 editor.emit() 通知其他模块editor.emit('dataProcessed', { result: data });促进插件间协作,实现松耦合架构。

完整自定义插件示例:

function AutoSavePlugin(options = {}) {
  const { interval = 30000 } = options;
  let timer;

  return {
    name: 'AutoSavePlugin',
    install(editor) {
      timer = setInterval(() => {
        const json = editor.toJSON(); // 假设有导出方法
        localStorage.setItem('rete-autosave', JSON.stringify(json));
        editor.emit('autosave', json);
      }, interval);

      // 返回销毁函数
      return () => {
        if (timer) clearInterval(timer);
      };
    }
  };
}

// 使用
editor.use(AutoSavePlugin({ interval: 60000 }));

第7章:序列化与持久化

7.1 导出编辑器数据(JSON 序列化)

方法/属性名称语法用途代码示例注意事项
toJSON()editor.toJSON()将当前编辑器状态(节点、连接等)序列化为 JSON 对象const data = editor.toJSON();
console.log(data);
包含 nodes 和 connections 数组,是标准导出方法。
节点数据结构{ id, label, x, y, inputs, outputs, controls }节点在 JSON 中的表示id: "node-1", label: "Add", x: 100, y: 200x, y 为画布坐标;inputs/outputs 包含端口 key、连接状态等。
连接数据结构{ id, source, sourceOutput, target, targetInput }连接在 JSON 中的表示source: "node-1", sourceOutput: "result", target: "node-2", targetInput: "num"明确指定源节点输出和目标节点输入的 key。
自定义字段序列化在节点类中添加可序列化字段将节点内部状态(如用户配置)包含在导出数据中class CustomNode extends Node {
  constructor() {
    super('Custom');
    this.customValue = 'default';
  }
}
需确保字段为基本类型(string, number, boolean, array, object)。
engine.toJSON()engine.toJSON()(部分实现)导出引擎相关配置或状态const engineData = engine.toJSON();并非所有引擎都支持,需查看具体插件文档。
手动过滤/处理JSON.stringify(data, replacer)自定义序列化过程,过滤敏感字段或转换数据const safeData = JSON.stringify(editor.toJSON(), (k, v) => k === 'password' ? undefined : v);使用 replacer 函数增强安全性或兼容性。

7.2 加载已有流程图(反序列化)

方法/步骤说明代码示例注意事项
fromJSON()将 JSON 数据加载回编辑器await editor.fromJSON(data);异步方法,需 await 等待完成。
注册节点类加载前必须确保所有用到的节点类已注册editor.register(CustomNode);
editor.register(AddNode);
否则对应节点无法创建,导致连接丢失或错误。
异步加载顺序先注册节点,再调用 fromJSON()editor.use(ConnectionPlugin());
editor.register(MyNode);
await editor.fromJSON(savedData);
顺序错误会导致数据无法正确解析。
处理连接丢失检查反序列化后是否有未连接的端口editor.nodes.forEach(node => {
  node.inputs.forEach(input => {
    if (!input.hasConnection()) console.warn('Unconnected input:', input);
  });
});
可能因节点类未注册或 Socket 类型不匹配导致。
恢复 UI 状态坐标、选中状态等通常由渲染器管理render.render(editor, container);fromJSON() 主要恢复逻辑结构,UI 渲染需后续触发。
错误处理捕获 fromJSON() 可能抛出的异常try { await editor.fromJSON(data); } catch (err) { console.error('Load failed:', err); }数据格式错误或缺失依赖时会失败。
版本兼容性处理不同版本间的数据结构变化在 JSON 中添加 version 字段,并在加载时做迁移处理if (data.version < 2) migrateToV2(data);

7.3 数据结构设计最佳实践

最佳实践说明示例/建议优势
使用语义化 Key为输入/输出端口使用清晰、有意义的 keyinputs.add('value', new NumberSocket()); 而非 inputs.add('in1', ...)提高可读性和维护性,便于调试。
模块化节点设计单个节点职责单一,避免过于复杂将”HTTP 请求 + JSON 解析 + 数据存储”拆分为三个节点提高复用性,降低耦合,易于测试和替换。
标准化 Socket 类型定义统一的 Socket 类型体系,避免随意创建const NumberSocket = new Socket('Number');
const StringSocket = new Socket('String');
保证连接安全,减少类型错误。
包含元数据在 JSON 中添加版本、作者、描述等元信息{ version: "1.0", author: "dev", description: "Calc flow", nodes: [...] }便于管理和追溯,支持向后兼容。
控制数据扁平化控件(Control)的状态尽量保持为简单值this.controls.add('text', new InputControl('text')); // value is string简化序列化,避免嵌套对象难以处理。
避免循环依赖设计节点连接时防止形成闭环,导致执行死锁使用 DAG(有向无环图)校验工具保证数据流可执行,避免无限递归。
预留扩展字段在节点或连接数据中预留 metadata 字段用于未来扩展node.metadata = { color: 'red', group: 'math' };无需修改核心结构即可增加新功能(如分组着色)。
轻量级存储仅序列化必要数据,避免冗余不存储临时 UI 状态(如 hover),只存核心逻辑和配置减少存储空间和传输开销,提升性能。
加密敏感数据对密码、密钥等敏感信息进行加密后再存储control.value = encrypt(password);提升数据安全性,符合合规要求。

第8章:实战项目:构建一个可视化逻辑编辑器

8.1 需求分析与模块划分

模块/功能需求描述技术选型与实现方案注意事项
核心编辑功能创建、连接、删除节点,拖拽画布,缩放Rete.js 核心 + AreaPlugin(缩放/平移) + ConnectionPlugin(连接)确保基础交互流畅,支持鼠标/触摸操作。
节点类型支持逻辑判断、数值计算、文本处理、数据输入/输出等基本节点设计 ConditionNode, MathNode, TextNode, InputNode, OutputNode 等节点职责单一,便于组合复用。
UI 框架集成使用 Vue 3 开发前端界面rete-vue-render-plugin + Vue 3 Composition API注意 Vue 3 的响应式机制与 Rete 节点状态同步。
上下文菜单右键点击画布或节点,提供”添加节点”、“删除”、“复制”等操作ContextMenuPlugin 自定义菜单项菜单项应根据上下文(空白处/节点上)动态变化。
小地图导航大型流程图中快速定位MinimapPlugin可选功能,提升用户体验。
执行引擎能运行构建的逻辑流程,支持同步/异步节点Rete.Engine + 自定义 worker 逻辑异步节点需返回 Promise。
调试功能显示节点执行状态(运行中、成功、失败),查看数据流自定义 control.emit() 传递状态,在 UI 中高亮反馈可结合日志面板输出执行信息。
保存/加载将流程图保存为 JSON,支持从本地或服务器加载editor.toJSON() / editor.fromJSON() + localStorage 或 fetch API加载前必须注册所有节点类。
数据校验连接时校验类型匹配,防止非法连接自定义 ConnectionPlugin 的 validator 函数如 Number 输出不能连接到 String 输入。

8.2 节点设计与注册

1. 基础节点类结构(以 MathNode 为例):

import { Node, Socket } from 'rete';

class MathNode extends Node {
  constructor() {
    super("MathOperation");

    // 输入端口
    const numSocket = new Socket('Number');
    this.addInput('num1', numSocket);
    this.addInput('num2', numSocket);

    // 输出端口
    this.addOutput('result', numSocket);

    // 控件(UI 输入)
    this.controls.add('operation', new SelectControl('Operation', ['+', '-', '*', '/']));
  }

  // 执行逻辑
  async worker(node, inputs, outputs) {
    const a = inputs['num1'] || 0;
    const b = inputs['num2'] || 0;
    const op = node.controls.get('operation').value;

    let result;
    switch (op) {
      case '+': result = a + b; break;
      case '-': result = a - b; break;
      case '*': result = a * b; break;
      case '/': result = b !== 0 ? a / b : 0; break;
      default: result = a + b;
    }

    return { result };
  }
}

2. 注册所有节点:

// 必须在 editor.fromJSON() 前完成注册
editor.register(MathNode);
editor.register(ConditionNode);
editor.register(InputNode);
editor.register(OutputNode);
// ... 其他节点

3. 自定义 UI 组件(Vue 3):

<!-- MathNodeView.vue -->
<template>
  <div class="math-node">
    <h4>数学运算</h4>
    <select :value="node.controls.get('operation').value" @change="onChange">
      <option value="+">+</option>
      <option value="-">-</option>
      <option value="*">*</option>
      <option value="/">/</option>
    </select>
    <!-- 端口由渲染器自动渲染 -->
  </div>
</template>

<script>
export default {
  props: ['node', 'emit'],
  methods: {
    onChange(e) {
      this.node.controls.get('operation').setValue(e.target.value);
      this.emit('update'); // 通知引擎可能需要重新执行
    }
  }
}
</script>

4. 绑定自定义 UI:

import { VueRenderPlugin } from 'rete-vue-render-plugin';
import MathNodeView from './MathNodeView.vue';

const render = VueRenderPlugin();
render.add(MathNode, MathNodeView); // 关联节点类与 UI 组件

8.3 流程执行与调试

1. 初始化并使用引擎:

import { Engine } from 'rete';

const engine = new Engine();

// 注册节点到引擎(必须与编辑器注册一致)
engine.register(MathNode);
engine.register(ConditionNode);
// ...

// 将引擎与编辑器关联(某些插件需要)
// editor.use(enginePlugin); // 取决于具体实现

2. 执行整个流程:

async function runFlow() {
  try {
    // 清除上次执行状态(如高亮)
    clearExecutionHighlights();

    // 启动执行
    await engine.process(); // 或 engine.execute(startNode, 'exec', data)

    console.log('流程执行完成');
  } catch (err) {
    console.error('执行出错:', err);
    showErrorDialog(err.message);
  }
}

3. 实现调试反馈(在节点 UI 中):

// 在节点 worker 开始时发送事件
async worker(node, inputs, outputs) {
  node.controls.get('status').setValue('running'); // 假设有状态控件
  emit('status', 'running');

  // ... 执行逻辑

  node.controls.get('status').setValue('success');
  emit('status', 'success');
  return { result };
}

// 在 UI 组件中监听
mounted() {
  this.node.on('status', (status) => {
    this.highlightColor = status === 'running' ? 'yellow' :
                         status === 'success' ? 'green' : 'red';
  });
}

8.4 保存与加载功能实现

1. 保存流程图:

function saveFlow() {
  const data = editor.toJSON(); // 获取 JSON 数据
  const jsonStr = JSON.stringify(data, null, 2); // 格式化

  // 方案1:下载到本地
  const blob = new Blob([jsonStr], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'flow.json';
  a.click();

  // 方案2:保存到 localStorage
  localStorage.setItem('saved-flow', jsonStr);

  // 方案3:上传到服务器
  // fetch('/api/save', { method: 'POST', body: jsonStr });
}

2. 加载流程图:

async function loadFlow(jsonData) {
  try {
    // 确保所有节点类已注册(关键!)
    registerAllNodes(editor); // 封装了所有 editor.register() 调用

    await editor.fromJSON(jsonData);
    console.log('流程图加载成功');

    // 重新渲染(如果需要)
    // render.render(editor, container);
  } catch (err) {
    console.error('加载失败:', err);
    alert('加载失败:' + err.message);
  }
}

// 从文件加载
function loadFromFile(file) {
  const reader = new FileReader();
  reader.onload = async (e) => {
    const data = JSON.parse(e.target.result);
    await loadFlow(data);
  };
  reader.readAsText(file);
}

// 从 localStorage 加载
function loadFromStorage() {
  const saved = localStorage.getItem('saved-flow');
  if (saved) {
    loadFlow(JSON.parse(saved));
  }
}

3. 初始化时尝试自动加载:

// 页面加载后
async function initEditor() {
  // ... 创建 editor, 注册插件

  // 尝试从 localStorage 恢复
  const autoLoad = localStorage.getItem('auto-load-on-start');
  if (autoLoad === 'true') {
    await loadFromStorage();
  }
}

第9章:性能优化与常见问题

9.1 大型流程图性能调优

优化策略说明实现方法与代码示例注意事项
虚拟化渲染 (Virtualization)只渲染视口内的节点,减少 DOM 节点数量使用 rete-vue-render-plugin 的 virtual 模式或自定义渲染器进行可见性判断需结合 AreaPlugin 获取视口范围,显著提升大型画布的渲染性能。
简化节点 UI减少每个节点的 DOM 元素和复杂样式避免在节点内使用过多嵌套、阴影、动画;使用轻量级组件复杂 UI 是性能瓶颈主因,尤其在移动端。
防抖连接/拖拽对频繁触发的事件(如鼠标移动)进行防抖处理lodash.debounce 包装 mousemove 事件:const debouncedMove = debounce(handleMove, 16);降低事件处理频率,避免卡顿。
减少 re-render避免不必要的组件重渲染(Vue/React)Vue: 使用 v-memoshouldComponentUpdate;React: React.memo确保节点状态变化时才更新 UI。
使用 Canvas 渲染用 Konva 或原生 Canvas 替代 DOM 渲染使用 rete-konva-render-pluginCanvas 性能远超 DOM,适合超大型流程图(>1000 节点)。
懒加载连接线连接线在节点被选中或悬停时才高亮/渲染初始渲染为细线或隐藏,通过事件动态更新样式减少初始绘制负担。
优化数据流避免全图执行,仅执行受影响的子图实现增量执行引擎或手动控制 engine.execute(node) 从特定节点开始减少不必要的计算,提升响应速度。
Web Worker 执行将复杂计算或引擎执行放入 Web Worker,避免阻塞主线程engine.process() 移至 Worker,通过 postMessage 通信适用于 CPU 密集型逻辑,提升 UI 流畅度。

9.2 内存泄漏预防

泄漏来源预防与解决方法代码示例注意事项
未清理事件监听所有通过 on() 添加的监听器,必须在销毁时用 off() 移除const handler = () => {}; editor.on('nodecreated', handler);
// 销毁时
editor.off('nodecreated', handler);
特别是自定义插件和 UI 组件中的监听。
插件未返回销毁函数自定义插件的 install 方法应返回清理函数install(editor) {
  const interval = setInterval(...);
  return () => clearInterval(interval);
}
编辑器销毁时会自动调用返回的函数。
DOM 引用残留手动创建的 DOM 元素未从文档移除const el = document.createElement('div');
container.appendChild(el);
// 销毁时
container.removeChild(el);
确保所有动态元素都有明确的挂载和卸载逻辑。
闭包引用事件处理器或定时器中引用了大对象或组件实例避免在 worker 或事件中直接引用 editor 或 node 的深层属性使用弱引用或仅传递必要数据。
缓存未清理节点或引擎的 cache 数据累积定期清理 node.cache 或执行后重置特别是异步节点,避免缓存过期数据。
循环引用对象 A 引用 B,B 又引用 A,导致 GC 无法回收检查数据结构,避免节点间直接强引用使用 WeakMap 或事件机制替代直接引用。
框架组件未卸载Vue/React 组件销毁时未清理 Rete 相关资源beforeUnmount / useEffect cleanup 中移除事件、销毁编辑器onBeforeUnmount(() => { editor.destroy(); });
监控工具使用浏览器开发者工具检测内存泄漏Chrome DevTools: Memory -> Heap Snapshot / Record Allocation Timeline定期检查是否存在对象持续增长。

9.3 跨框架兼容性问题

问题类型描述与解决方案代码示例或说明注意事项
渲染器不匹配VueRenderPlugin 无法在 React 项目中使用必须使用对应框架的渲染插件:
- Vue 项目:rete-vue-render-plugin
- React 项目:rete-react-render-plugin
混用会导致渲染失败或空白。
版本冲突Rete.js 或插件版本与框架版本不兼容(如 Vue 2 vs Vue 3)仔细查看插件文档的 peerDependencies
例如:rete-vue-render-plugin@^2.0.0 支持 Vue 3
使用 npm ls 检查依赖树,避免多版本共存。
TypeScript 类型问题不同框架的类型定义(.d.ts)冲突或缺失确保安装了正确的类型包:@types/rete 或插件自带类型
必要时手动扩展类型
shims-vue.d.tsglobal.d.ts 中声明模块。
状态同步问题框架的响应式系统与 Rete 节点状态不同步Vue 3: 使用 ref / reactive 包装节点数据,并在变更时触发更新
React: 使用 useState + useEffect
避免直接修改非响应式对象。
事件机制差异框架的事件系统(如 Vue 的 $emit)与 Rete 的 emit 混淆明确区分:
- Rete 事件:editor.emit('nodeselected', node)
- 组件事件:this.$emit('update')
建议封装一层事件总线避免混淆。
构建工具配置Vite/Webpack 对 ESM/CJS 模块处理不一致,导致导入失败vite.config.jswebpack.config.js 中正确配置 resolve.aliasexternals特别是使用 rete 和插件时,确保路径正确。
SSR (服务端渲染) 不支持Rete.js 依赖 DOM 和浏览器 API,无法在 Node.js 环境运行在 SSR 项目中使用 v-if / useEffect 延迟初始化,仅在客户端挂载<div v-if="isClient"><ReteEditor /></div>
移动端适配触摸事件与鼠标事件处理逻辑不同确保 AreaPlugin 和 ConnectionPlugin 支持触摸(通常默认支持)测试拖拽、缩放等手势在移动设备上的表现。