From 3dda7cfdf38aa2fe6daca93f72e9f3d64cb7f647 Mon Sep 17 00:00:00 2001 From: xiamidaxia Date: Tue, 17 Jun 2025 17:53:17 +0800 Subject: [PATCH] feat: use-node-render add id,type,data,updateData (#384) --- .../en/guide/advanced/fixed-layout/node.mdx | 28 +++-- .../en/guide/advanced/free-layout/node.mdx | 26 ++-- .../zh/guide/advanced/fixed-layout/node.mdx | 25 ++-- .../zh/guide/advanced/free-layout/node.mdx | 25 ++-- .../free-layout-core/src/hooks/typings.ts | 10 ++ .../src/hooks/use-node-render.tsx | 114 +++++++++++++----- .../src/hooks/use-node-render.tsx | 42 ++++++- 7 files changed, 190 insertions(+), 80 deletions(-) diff --git a/apps/docs/src/en/guide/advanced/fixed-layout/node.mdx b/apps/docs/src/en/guide/advanced/fixed-layout/node.mdx index ed402a1b..b1224d40 100644 --- a/apps/docs/src/en/guide/advanced/fixed-layout/node.mdx +++ b/apps/docs/src/en/guide/advanced/fixed-layout/node.mdx @@ -96,6 +96,12 @@ export const nodeRegistries: FlowNodeRegistry[] = [ Get node-related methods through [useNodeRender](/api/hooks/use-node-render.html) +```tsx pure +function BaseNode() { + const { id, type, data, updateData, node } = useNodeRender() +} +``` + ## Creating Nodes Create through [FlowOperationService](/api/services/flow-operation-service.html) @@ -163,23 +169,19 @@ function BaseNode({ node }) { ```tsx pure function BaseNode() { - const { form } = useNodeRender(); - // Corresponding node data - console.log(form.values) - - // Monitor node data changes - useEffect(() => { - const toDispose = form.onFormValuesChange(() => { - // changed - }) - return () => toDispose.dispose() - }, [form]) + const { data, updateData } = useNodeRender(); + // Corresponds to node's data + console.log(data) function onChange(e) { - form.setValueIn('title', e.target.value) + updateData({ + ...data, + title: e.target.value + }) } - return + return } + ``` - Update form data through Field, see [Form Usage](/guide/advanced/form.html) for details diff --git a/apps/docs/src/en/guide/advanced/free-layout/node.mdx b/apps/docs/src/en/guide/advanced/free-layout/node.mdx index 8c749652..fcf0c927 100644 --- a/apps/docs/src/en/guide/advanced/free-layout/node.mdx +++ b/apps/docs/src/en/guide/advanced/free-layout/node.mdx @@ -36,6 +36,12 @@ In free layout scenarios, node definition is used to declare node's initial posi Get node-related methods through [useNodeRender](api/hooks/use-node-render.html) +```tsx pure +function BaseNode() { + const { id, type, data, updateData, node } = useNodeRender() +} +``` + ## Create Node - Through [WorkflowDocument](/api/core/workflow-document.html) @@ -96,23 +102,19 @@ function BaseNode({ node }) { ```tsx pure function BaseNode() { - const { form } = useNodeRender(); + const { data, updateData } = useNodeRender(); // Corresponds to node's data - console.log(form.values) - - // Listen to node data changes - useEffect(() => { - const toDispose = form.onFormValuesChange(() => { - // changed - }) - return () => toDispose.dispose() - }, [form]) + console.log(data) function onChange(e) { - form.setValueIn('title', e.target.value) + updateData({ + ...data, + title: e.target.value + }) } - return + return } + ``` - Update form data through Field, see details in [Form Usage](/guide/advanced/form.html) diff --git a/apps/docs/src/zh/guide/advanced/fixed-layout/node.mdx b/apps/docs/src/zh/guide/advanced/fixed-layout/node.mdx index 5a4f0b37..8efef272 100644 --- a/apps/docs/src/zh/guide/advanced/fixed-layout/node.mdx +++ b/apps/docs/src/zh/guide/advanced/fixed-layout/node.mdx @@ -97,6 +97,12 @@ export const nodeRegistries: FlowNodeRegistry[] = [ 通过 [useNodeRender](/api/hooks/use-node-render.html) 获取节点相关方法 +```tsx pure +function BaseNode() { + const { id, type, data, updateData, node } = useNodeRender() +} +``` + ## 创建节点 通过 [FlowOperationService](/api/services/flow-operation-service.html) 创建 @@ -164,22 +170,17 @@ function BaseNode({ node }) { ```tsx pure function BaseNode() { - const { form } = useNodeRender(); + const { data, updateData } = useNodeRender(); // 对应节点的 data 数据 - console.log(form.values) - - // 监听节点的数据变化 - useEffect(() => { - const toDispose = form.onFormValuesChange(() => { - // changed - }) - return () => toDispose.dispose() - }, [form]) + console.log(data) function onChange(e) { - form.setValueIn('title', e.target.value) + updateData({ + ...data, + title: e.target.value + }) } - return + return } ``` - 通过 Field 更新表单数据, 详细见 [表单的使用](/guide/advanced/form.html) diff --git a/apps/docs/src/zh/guide/advanced/free-layout/node.mdx b/apps/docs/src/zh/guide/advanced/free-layout/node.mdx index 636c67fc..8f246515 100644 --- a/apps/docs/src/zh/guide/advanced/free-layout/node.mdx +++ b/apps/docs/src/zh/guide/advanced/free-layout/node.mdx @@ -37,6 +37,12 @@ const nodeData: FlowNodeJSON = { 通过 [useNodeRender](api/hooks/use-node-render.html) 获取节点相关方法 +```tsx pure +function BaseNode() { + const { id, type, data, updateData, node } = useNodeRender() +} +``` + ## 创建节点 - 通过 [WorkflowDocument](/api/core/workflow-document.html) 创建 @@ -96,22 +102,17 @@ function BaseNode({ node }) { ```tsx pure function BaseNode() { - const { form } = useNodeRender(); + const { data, updateData } = useNodeRender(); // 对应节点的 data 数据 - console.log(form.values) - - // 监听节点的数据变化 - useEffect(() => { - const toDispose = form.onFormValuesChange(() => { - // changed - }) - return () => toDispose.dispose() - }, [form]) + console.log(data) function onChange(e) { - form.setValueIn('title', e.target.value) + updateData({ + ...data, + title: e.target.value + }) } - return + return } ``` - 通过 Field 更新表单数据, 详细见 [表单的使用](/guide/advanced/form.html) diff --git a/packages/canvas-engine/free-layout-core/src/hooks/typings.ts b/packages/canvas-engine/free-layout-core/src/hooks/typings.ts index c8b42f2e..f158f071 100644 --- a/packages/canvas-engine/free-layout-core/src/hooks/typings.ts +++ b/packages/canvas-engine/free-layout-core/src/hooks/typings.ts @@ -4,10 +4,20 @@ import { FlowNodeEntity } from '@flowgram.ai/document'; import { type WorkflowPortEntity } from '../entities'; export interface NodeRenderReturnType { + id: string; + type: string | number; /** * 当前节点 */ node: FlowNodeEntity; + /** + * 节点 data 数据 + */ + data: any; + /** + * 更新节点 data 数据 + */ + updateData: (newData: any) => void; /** * 节点选中 */ diff --git a/packages/canvas-engine/free-layout-core/src/hooks/use-node-render.tsx b/packages/canvas-engine/free-layout-core/src/hooks/use-node-render.tsx index b11e9de6..1ca0420a 100644 --- a/packages/canvas-engine/free-layout-core/src/hooks/use-node-render.tsx +++ b/packages/canvas-engine/free-layout-core/src/hooks/use-node-render.tsx @@ -25,6 +25,7 @@ function checkTargetDraggable(el: any): boolean { !el.closest('.flow-canvas-not-draggable') ); } + export function useNodeRender(nodeFromProps?: WorkflowNodeEntity): NodeRenderReturnType { const node = nodeFromProps || useContext(PlaygroundEntityContext); const renderData = node.getData(FlowNodeRenderData)!; @@ -33,6 +34,9 @@ export function useNodeRender(nodeFromProps?: WorkflowNodeEntity): NodeRenderRet const dragService = useService(WorkflowDragService); const selectionService = useService(WorkflowSelectService); const isDragging = useRef(false); + const [formValueVersion, updateFormValueVersion] = useState(0); + const formValueDependRef = useRef(false); + formValueDependRef.current = false; const nodeRef = useRef(null); const [linkingNodeId, setLinkingNodeId] = useState(''); @@ -126,35 +130,85 @@ export function useNodeRender(nodeFromProps?: WorkflowNodeEntity): NodeRenderRet const toggleExpand = useCallback(() => { renderData.toggleExpand(); }, [renderData]); + const selected = selectionService.isSelected(node.id); + const activated = selectionService.isActivated(node.id); + const expanded = renderData.expanded; + useEffect(() => { + const toDispose = form?.onFormValuesChange(() => { + if (formValueDependRef.current) { + updateFormValueVersion((v) => v + 1); + } + }); + return () => toDispose?.dispose(); + }, [form]); - return { - node, - selected: selectionService.isSelected(node.id), - activated: selectionService.isActivated(node.id), - expanded: renderData.expanded, - startDrag, - ports: portsData.allPorts, - deleteNode, - selectNode, - readonly, - linkingNodeId, - nodeRef, - onFocus, - onBlur, - getExtInfo, - updateExtInfo, - toggleExpand, - get form() { - if (!form) return undefined; - return { - ...form, - get values() { - return form.values!; - }, - get state() { - return formState; - }, - }; - }, - }; + return useMemo( + () => ({ + id: node.id, + type: node.flowNodeType, + get data() { + if (form) { + formValueDependRef.current = true; + return form.values; + } + return getExtInfo(); + }, + updateData(values: any) { + if (form) { + form.updateFormValues(values); + } else { + updateExtInfo(values); + } + }, + node, + selected, + activated, + expanded, + startDrag, + get ports() { + return portsData.allPorts; + }, + deleteNode, + selectNode, + readonly, + linkingNodeId, + nodeRef, + onFocus, + onBlur, + getExtInfo, + updateExtInfo, + toggleExpand, + get form() { + if (!form) return undefined; + return { + ...form, + get values() { + formValueDependRef.current = true; + return form.values!; + }, + get state() { + return formState; + }, + }; + }, + }), + [ + node, + selected, + activated, + expanded, + startDrag, + deleteNode, + selectNode, + readonly, + linkingNodeId, + nodeRef, + onFocus, + onBlur, + getExtInfo, + updateExtInfo, + toggleExpand, + formValueVersion, + ] + ); } diff --git a/packages/client/fixed-layout-editor/src/hooks/use-node-render.tsx b/packages/client/fixed-layout-editor/src/hooks/use-node-render.tsx index 95b000ad..ac749e3c 100644 --- a/packages/client/fixed-layout-editor/src/hooks/use-node-render.tsx +++ b/packages/client/fixed-layout-editor/src/hooks/use-node-render.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useContext, useMemo, useRef } from 'react'; +import React, { useCallback, useEffect, useContext, useMemo, useRef, useState } from 'react'; import { useObserve } from '@flowgram.ai/reactive'; import { useStartDragNode } from '@flowgram.ai/fixed-drag-plugin'; @@ -17,6 +17,16 @@ import { import { FlowOperationService } from '../types'; export interface NodeRenderReturnType { + id: string; + type: string | number; + /** + * 节点 data 数据 + */ + data: any; + /** + * 更新节点 data 数据 + */ + updateData: (newData: any) => void; /** * BlockOrderIcon节点,一般用于分支的第一个占位节点 */ @@ -100,6 +110,9 @@ export function useNodeRender(nodeFromProps?: FlowNodeEntity): NodeRenderReturnT const playground = usePlayground(); const isBlockOrderIcon = renderNode.flowNodeType === FlowNodeBaseType.BLOCK_ORDER_ICON; const isBlockIcon = renderNode.flowNodeType === FlowNodeBaseType.BLOCK_ICON; + const [formValueVersion, updateFormValueVersion] = useState(0); + const formValueDependRef = useRef(false); + formValueDependRef.current = false; // 在 BlockIcon 情况,如果在触发 fromJSON 时候更新表单数据导致刷新节点会存在 renderNode.parent 为 undefined,所以这里 nodeCache 进行缓存 const node = (isBlockOrderIcon || isBlockIcon ? renderNode.parent! : renderNode) || nodeCache.current; @@ -154,10 +167,35 @@ export function useNodeRender(nodeFromProps?: FlowNodeEntity): NodeRenderReturnT return () => dispose?.dispose(); }, [renderNode, isBlockIcon, isBlockOrderIcon]); + useEffect(() => { + const toDispose = form?.onFormValuesChange(() => { + if (formValueDependRef.current) { + updateFormValueVersion((v) => v + 1); + } + }); + return () => toDispose?.dispose(); + }, [form]); + const readonly = playground.config.readonly; return useMemo( () => ({ + id: node.id, + type: node.flowNodeType, + get data() { + if (form) { + formValueDependRef.current = true; + return form.values; + } + return getExtInfo(); + }, + updateData(values: any) { + if (form) { + form.updateFormValues(values); + } else { + updateExtInfo(values); + } + }, node, isBlockOrderIcon, isBlockIcon, @@ -177,6 +215,7 @@ export function useNodeRender(nodeFromProps?: FlowNodeEntity): NodeRenderReturnT return { ...form, get values() { + formValueDependRef.current = true; return form.values!; }, get state() { @@ -202,6 +241,7 @@ export function useNodeRender(nodeFromProps?: FlowNodeEntity): NodeRenderReturnT toggleExpand, form, formState, + formValueVersion, ] ); }