Reflex Flow Hooks 编程指南:通过 rxe.flow.api 全面操控 Flow 实例
【免费下载链接】reflex🕸️ Web apps in pure Python 🐍项目地址: https://gitcode.com/GitHub_Trending/re/reflex
导读
本文深入讲解 Reflex Enterprise 中rxe.flow.api模块提供的 Flow Hooks API——这是一组封装自 React FlowuseReactFlowHook 的 Python 接口,用于在 Flow 组件之外读取、修改节点与边、完成屏幕坐标与画布坐标的互转,以及查询连接状态。读完本文,你将掌握全部节点、边、视口与状态类 Hooks 的签名与语义,并能在"拖拽落点新建节点""自定义 Handle 限制连接数"等真实交互场景中直接落地使用。
适用前提:本文所有示例基于
reflex_enterprise(Reflex Enterprise)扩展,需在应用中使用rxe.flow.provider包裹rxe.flow组件,Hooks 方可正常工作。源码佐证均来自当前仓库docs/enterprise/react_flow/目录下的配套文档。
一、Hooks 是什么:rxe.flow.api模块概览
rxe.flow.api是 Reflex Enterprise 提供的面向 Flow 实例的编程接口模块。从文档定义看,这些 Hooks 本质上是 React Flow 中useReactFlowHook 的 Python 侧包装(参见 hooks.md)。useReactFlow是 React Flow 的核心 Hook,它让开发者能够在<ReactFlow />组件外部访问画布实例的内部状态与操作能力;rxe.flow.api则把这些底层能力以 Python 函数形式暴露出来,使你在 Reflex 的组件代码中即可直接调用。
使用前提:必须包裹在 Provider 中
文档明确强调:
These hooks rely on the flow being wrapped in
rxe.flow.provider.
即所有 Hooks 都依赖 Flow 被rxe.flow.provider包裹。FlowProvider是一个 Context Provider,它使得在<ReactFlow />组件之外也能访问 Flow 的内部状态——这正是 Hooks 得以工作的机制。从 components.md 可以看到,rxe.flow.provider本身不接受任何事件处理器,所有事件处理器(on_nodes_change、on_edges_change、on_connect等)都必须设置在内部的rxe.flow组件上:
rx.box( rxe.flow.provider( rxe.flow( rxe.flow.background(), nodes=FlowState.nodes, edges=FlowState.edges, on_nodes_change=lambda changes: FlowState.set_nodes( rxe.flow.util.apply_node_changes(FlowState.nodes, changes) ), on_edges_change=lambda changes: FlowState.set_edges( rxe.flow.util.apply_edge_changes(FlowState.edges, changes) ), fit_view=True, ) ), height="100vh", width="100vw", )Hooks 在何处执行
与rxe.flow.util中的工具函数(如apply_node_changes、add_edge)类似,Hooks 的求值发生在客户端。以坐标转换为例,文档明确指出:转换在客户端完成,转换得到的XYPosition会作为参数传递给 State 事件处理器。这也意味着 Hooks 通常出现在组件代码的 lambda 中,而不是写在@rx.event处理器内部——State 侧只负责接收 Hooks 产出的结果并存储。
二、Node Hooks:读写与更新节点
Node Hooks 用于对流程画布中的节点集合进行查询、整体替换、追加、按 ID 查询与局部更新。完整清单如下:
| Hook | 签名 | 说明 |
|---|---|---|
get_nodes() | () -> list[Node] | 返回流程中所有节点的数组 |
set_nodes(nodes) | (nodes) -> None | 设置流程中的节点(整体替换) |
add_nodes(nodes) | (nodes) -> None | 向流程中追加节点 |
get_node(id) | (id) -> Node | 按 ID 返回单个节点 |
update_node(id, node_update, replace=False) | (id, node_update, replace=False) -> None | 更新流程中的节点 |
update_node_data(id, data_update, replace=False) | (id, data_update, replace=False) -> None | 更新节点的data字段 |
replace参数的语义
update_node与update_node_data都接受一个replace=False的布尔参数,它控制更新时的合并策略:
replace=False(默认):采用浅合并(merge)语义。传入的node_update/data_update字典会与目标节点/数据的现有字段进行合并,未提及的字段保持不变;replace=True:完全用传入的新字典覆盖目标节点/数据。
这一设计让"只改一个字段"与"整体重建"两种需求都有对应的简洁写法。例如,只想把某个节点的标签改掉,可以update_node("node-2", {"data": {"label": "New Label"}})配合默认合并行为;想彻底重置节点数据则传replace=True。
与受控模式的关系
在受控 Flow(同时传入nodes与edges)中,State 是画布的"唯一事实来源",画布渲染的正是 State 所持有的内容(参见 interactivity.md)。Node Hooks 正是建立在受控模式之上的高效操作手段:与其手动构造整个节点列表再通过 setter 回写,不如直接用update_node等 Hooks 在客户端完成细粒度修改,再把结果同步回 State。
三、Edge Hooks:读写与更新边
Edge Hooks 与 Node Hooks 一一对应,用于操作流程中的边:
| Hook | 签名 | 说明 |
|---|---|---|
get_edges() | () -> list[Edge] | 返回流程中所有边的数组 |
set_edges(edges) | (edges) -> None | 设置流程中的边(整体替换) |
add_edges(edges) | (edges) -> None | 向流程中追加边 |
get_edge(id) | (id) -> Edge | 按 ID 返回单条边 |
update_edge(id, edge_update, replace=False) | (id, edge_update, replace=False) -> None | 更新流程中的边 |
update_edge_data(id, data_update, replace=False) | (id, data_update, replace=False) -> None | 更新边的data字段 |
update_edge与update_edge_data的replace参数语义与节点版本完全一致(默认浅合并,True为整体覆盖)。边对象的核心字段包括id、source(源节点 ID)、target(目标节点 ID)、label、type(边类型,如step)、animated等,可参见 nodes.md 与 edges.md 中对节点/边结构的定义。
四、Viewport Hooks:屏幕坐标与画布坐标互转
视口(Viewport)是包含整个流程的可见区域,节点通过x/y坐标定位,缩放则改变zoom级别(参见 overview.md)。rxe.flow.api提供两个方向相反的坐标转换 Hook:
| Hook | 签名 | 说明 |
|---|---|---|
screen_to_flow_position(x, y, snap_to_grid=False) | (x, y, snap_to_grid=False) -> XYPosition | 将屏幕像素坐标转换为流程(画布)坐标 |
flow_to_screen_position(x, y) | (x, y) -> XYPosition | 将流程画布内的坐标转换为屏幕像素坐标 |
screen_to_flow_position的典型用途
文档给出的核心场景是:把事件中的指针坐标转换为画布坐标——例如当用户把一条未完成的连接拖放到画布上时,在落点处新建一个节点:
rxe.flow( ..., on_connect_end=lambda connection_status, event: FlowState.handle_connect_end( connection_status, rxe.flow.api.screen_to_flow_position( x=event.client_x, y=event.client_y, ), ), )这里event.client_x/event.client_y是浏览器事件对象中的指针位置(屏幕坐标系),而节点在画布中的position使用的是画布坐标系,二者在缩放、平移后并不一致,因此必须经screen_to_flow_position转换后才能正确放置节点。
坐标转换的底层细节:正如文档所注,转换发生在客户端,转换后的XYPosition会作为参数传给 State 事件处理器。这解释了为何示例中handle_connect_end事件处理器的签名里可以多出一个flow_position: XYPosition参数——它是 Hooks 在客户端求值后拼接到事件参数中的。
完整实战:Add Node on Edge Drop
examples.md 提供了这一场景的完整实现。核心思路是:当用户在画布空白处松开一条无效连接时,用screen_to_flow_position算出落点坐标,随后在 State 中追加一个新节点,并补一条从源节点指向新节点的边:
class AddNodesOnEdgeDropState(rx.State): nodes: rx.Field[list[Node]] = rx.field(default_factory=lambda: initial_nodes) edges: rx.Field[list[Edge]] = rx.field(default_factory=list) node_id: int = 1 @rx.event def handle_connect_end( self, connection_status: NoConnection | ConnectionInProgress, event: rx.event.PointerEventInfo, flow_position: XYPosition, ): if not connection_status["isValid"]: node_id = str(self.node_id) self.increment() self.nodes.append({ "id": node_id, "position": flow_position, "data": {"label": f"Node {node_id}"}, "origin": (0.5, 0.0), "style": node_style, }) self.edges.append({ "id": node_id, "source": connection_status["fromNode"]["id"], "target": node_id, "style": node_style, })在组件侧,事件处理器通过 lambda 调用screen_to_flow_position,把客户端坐标转换的结果与连接状态、事件对象一并交给 State:
rxe.flow( ..., on_connect_end=( lambda connection_status, event: ( AddNodesOnEdgeDropState.handle_connect_end( connection_status, event, rxe.flow.api.screen_to_flow_position( x=event.client_x, y=event.client_y, ), ) ) ), ... )几个值得注意的要点:
- 校验连接是否有效:
connection_status是NoConnection | ConnectionInProgress联合类型,通过connection_status["isValid"]判断连接是否合法,只有无效(悬空拖放)才需要新建节点; - 错误边界的处理:
handle_connect_end中先自增node_id再使用,保证新节点 ID 唯一; origin与node_origin的配合:节点使用"origin": (0.5, 0.0)(水平居中、顶部对齐),与组件上的node_origin=(0.5, 0.0)保持一致,使新节点以拖放落点为中心锚定。
五、Other Hooks:状态导出、相交检测与连接查询
除节点、边、视口三类 Hooks 外,rxe.flow.api还提供四个通用 Hooks:
| Hook | 签名 | 说明 |
|---|---|---|
to_object() | () -> dict | 将 React Flow 的完整状态转换为 JSON 对象 |
get_intersecting_nodes(node, partially=True, nodes=None) | (node, partially=True, nodes=None) -> list[Node] | 找出与指定节点/矩形相交的所有节点 |
get_node_connections(id=None, handle_type=None, handle_id=None) | (id=None, handle_type=None, handle_id=None) -> list[Connection] | 返回指定节点、Handle 类型("source"或"target")或 Handle ID 上的连接数组 |
get_connection() | () -> Connection | 当存在进行中的连接交互时,返回当前连接状态 |
to_object():导出整个流程状态
to_object()适合需要序列化、持久化或调试当前流程的场景——它一次性导出节点、边、视口等全部 React Flow 内部状态为 JSON 对象,可直接存储到后端或用于"保存/恢复"功能。
get_intersecting_nodes():相交检测
get_intersecting_nodes用于碰撞/相交判断,可用于实现"拖拽节点靠近目标时高亮"、"自动吸附分组"等交互。参数含义:
node:用于检测相交的节点或矩形(Node或矩形坐标范围);partially=True:是否允许部分相交(True表示只要部分重叠即算相交,False表示必须完全包含);nodes=None:可选,限定在指定节点集合内检测,缺省时检测全部节点。
get_node_connections():按节点/Handle 维度查询连接
get_node_connections是最精细的连接查询接口,三个过滤参数可自由组合:
id:只返回与该节点相关的连接;handle_type:"source"(源端)或"target"(目标端);handle_id:只返回与该特定 Handle 相关的连接。
实战:用get_node_connections实现连接数限制
examples.md 中的 "Connection Limit on Custom Node" 示例展示了get_node_connections的典型用法:自定义节点上只允许一条连接,一旦已有连接就禁用 Handle。其核心是在@rx.memo缓存的自定义 Handle 组件中动态查询当前连接数:
@rx.memo def custom_handle( type: rx.Var[HandleType], position: rx.Var[Position], connection_count: rx.Var[int] ) -> rxe.components.flow.Handle: connections = rxe.flow.api.get_node_connections() return rxe.flow.handle( type=type, position=position, connection_count=connection_count, is_connectable=connections.length() < connection_count.guess_type(), )关键点:
get_node_connections()不带任何过滤参数时返回当前节点(Handle 所在节点)的全部连接;connections.length()与传入的connection_count(此处为1)比较,动态计算is_connectable——已满则不可再连;- 由于自定义节点组件在画布中会被多次实例化,借助
@rx.memo缓存组件、以参数驱动渲染,可避免重复求值开销; - 该 Handle 随后被
custom_node以custom_handle(type="target", position="left", connection_count=1)的方式使用,从而实现了"target 端只允许一条边"的约束。
get_connection():读取进行中的连接状态
get_connection()返回当前活动连接交互的状态(即用户正在从某个 Handle 拖拽连接线但尚未松开时的连接对象)。它常与on_connect_start等事件配合,用于在拖拽过程中动态渲染提示、高亮可连接的目标 Handle 等场景。
六、Hooks 与事件处理器的协作模式
综合上述内容,Hooks 在应用中的协作方式可以归纳为三类模式:
模式一:客户端求值 + 参数传递(坐标类)screen_to_flow_position等转换型 Hooks 在 lambda 中求值,结果作为附加参数传给 State 事件处理器。转换在客户端完成,State 收到的是已就绪的数据。
模式二:组件内动态查询(查询类)get_node_connections、get_intersecting_nodes、get_connection等查询型 Hooks 用于渲染期决策(如计算is_connectable),通常配合@rx.memo或@rx.event处理器使用,直接影响组件当前的渲染输出。
模式三:与rxe.flow.util工具函数协同(变更类)节点/边 Hooks 与rxe.flow.util中的变更工具(apply_node_changes、apply_edge_changes、add_edge,见 utils.md)分工明确:util负责把画布产生的事件变更(拖拽、删除、新建连接)翻译为新的节点/边列表,而 Hooks 负责程序化地读写画布实例。二者共同支撑受控 Flow 的完整闭环。
一个融合示例
将两者结合,可以在on_connect中既记录新边、又通过 Hooks 查询连接状态做后续逻辑:
on_connect=lambda connection: FlowState.set_edges( rxe.flow.util.add_edge(connection, FlowState.edges) ),而当需要程序化地"查询后修改"时(例如在某个@rx.event处理器中基于现有节点决定新增位置),可以先get_nodes()拿到当前列表,再调用add_nodes(...)或update_node(...),最终把结果写回 State。
七、最佳实践与注意事项
- 务必包裹 Provider:所有
rxe.flow.apiHooks 都要求 Flow 位于rxe.flow.provider之内,否则无法访问 Flow 实例; - 事件处理器放对位置:
rxe.flow.provider不接受事件处理器,所有交互事件必须配置在内部的rxe.flow上; - Hooks 在客户端执行:坐标转换等结果通过事件参数回传 State,不要试图在
@rx.event处理器内直接调用需要客户端上下文的 Hooks; replace参数按需选择:默认浅合并适合局部更新;整体重建时才用replace=True,避免误覆盖其他字段;- 注意受控/非受控模式:Hooks 面向的是 Flow 实例的实际状态,在受控模式(传入
nodes/edges)下应始终通过on_nodes_change/on_edges_change与rxe.flow.util.apply_*_changes保持 State 与画布同步(详见 interactivity.md); - 容器必须有尺寸:Flow 会填充父容器,
rx.box包裹层需显式设置height与width(如height="100vh"、width="100vw"),否则画布不可见。
结语
rxe.flow.api把 React Flow 的useReactFlow能力完整地带入 Reflex 的 Python 世界:节点与边的增删改查、屏幕/画布坐标互转、相交检测与连接查询一应俱全。配合rxe.flow.provider与rxe.flow.util,你可以在受控 Flow 中构建"拖放即建节点""连接数受限的 Handle"等高级交互。更完整的可运行示例请参阅 examples.md,组件与 Provider 的完整 Props 说明见 components.md。
【免费下载链接】reflex🕸️ Web apps in pure Python 🐍项目地址: https://gitcode.com/GitHub_Trending/re/reflex
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考