feat: use-node-render add id,type,data,updateData (#384)

This commit is contained in:
xiamidaxia 2025-06-17 17:53:17 +08:00 committed by GitHub
parent 77d8a893cb
commit 3dda7cfdf3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 190 additions and 80 deletions

View File

@ -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 <input value={form.getValueIn('title')} onChange={onChange}/>
return <input value={data.title} onChange={onChange}/>
}
```
- Update form data through Field, see [Form Usage](/guide/advanced/form.html) for details

View File

@ -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 <input value={form.getValueIn('title')} onChange={onChange}/>
return <input value={data.title} onChange={onChange}/>
}
```
- Update form data through Field, see details in [Form Usage](/guide/advanced/form.html)

View File

@ -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 <input value={form.getValueIn('title')} onChange={onChange}/>
return <input value={data.title} onChange={onChange}/>
}
```
- 通过 Field 更新表单数据, 详细见 [表单的使用](/guide/advanced/form.html)

View File

@ -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 <input value={form.getValueIn('title')} onChange={onChange}/>
return <input value={data.title} onChange={onChange}/>
}
```
- 通过 Field 更新表单数据, 详细见 [表单的使用](/guide/advanced/form.html)

View File

@ -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;
/**
*
*/

View File

@ -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<WorkflowNodeEntity>(PlaygroundEntityContext);
const renderData = node.getData(FlowNodeRenderData)!;
@ -33,6 +34,9 @@ export function useNodeRender(nodeFromProps?: WorkflowNodeEntity): NodeRenderRet
const dragService = useService<WorkflowDragService>(WorkflowDragService);
const selectionService = useService<WorkflowSelectService>(WorkflowSelectService);
const isDragging = useRef(false);
const [formValueVersion, updateFormValueVersion] = useState<number>(0);
const formValueDependRef = useRef(false);
formValueDependRef.current = false;
const nodeRef = useRef<HTMLDivElement | null>(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,
]
);
}

View File

@ -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<number>(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,
]
);
}