Просмотр исходного кода

docs: improve node form docs and add english version for both guide and examples (#126)

* docs: add hooks for node form

* docs: fix small imperfection in node form doc

* docs: node form english docs

* docs: improve node form examples and add english version

* docs: better node form doc
YuanHeDx 9 месяцев назад
Родитель
Сommit
52bbf2794d

+ 1 - 1
apps/docs/components/node-form/effect/node-registry.tsx

@@ -37,7 +37,7 @@ const render = () => (
     </Field>
     <Field name="field3">
       {({ field }: FieldRenderProps<string>) => (
-        <FieldWrapper title="Controlled by other fields">
+        <FieldWrapper title="Field 3">
           <Input size={'small'} {...field} />
         </FieldWrapper>
       )}

+ 1 - 1
apps/docs/components/node-form/effect/preview.tsx

@@ -49,7 +49,7 @@ const render = () => (
     </Field>
     <Field name="field3">
       {({ field }: FieldRenderProps<string>) => (
-        <FieldWrapper title="Controlled by other fields">
+        <FieldWrapper title="Field 3">
           <Input size={'small'} {...field} />
         </FieldWrapper>
       )}

+ 5 - 0
apps/docs/src/en/examples/_meta.json

@@ -13,5 +13,10 @@
     "type": "dir",
     "name": "free-layout",
     "label": "Free Layout"
+  },
+  {
+    "type": "dir",
+    "name": "node-form",
+    "label": "Node Form"
   }
 ]

+ 6 - 0
apps/docs/src/en/examples/node-form/_meta.json

@@ -0,0 +1,6 @@
+[
+  "basic",
+  "effect",
+  "array",
+  "dynamic"
+]

+ 16 - 0
apps/docs/src/en/examples/node-form/array.mdx

@@ -0,0 +1,16 @@
+---
+outline: false
+---
+
+
+# Array
+
+import { NodeFormArrayPreview } from '../../../../components/node-form/array/preview';
+
+The following example demonstrates the basic usage of arrays, including:
+- Basic implementation (rendering, adding and removing items).
+- How to configure validation logic for each array item. In this case, the validation rule is that each item should not exceed 8 English characters.
+- How to configure side effects for each array item. Here, the side effect outputs `${name} value init to ${value}` to the console during initialization, and `${name} value changed to ${value}` when the value changes.
+- How to swap array items.
+
+<NodeFormArrayPreview />

+ 16 - 0
apps/docs/src/en/examples/node-form/basic.mdx

@@ -0,0 +1,16 @@
+---
+outline: false
+---
+
+
+# Basic Usage
+
+import { NodeFormBasicPreview } from '../../../../components';
+
+<div>
+  This example demonstrates several basic form usages:
+   - Form component rendering
+   - Required field validation
+   - Default value setting
+</div>
+<NodeFormBasicPreview />

+ 16 - 0
apps/docs/src/en/examples/node-form/dynamic.mdx

@@ -0,0 +1,16 @@
+---
+outline: false
+---
+
+
+# Dynamic Field
+
+import { NodeFormDynamicPreview } from '../../../../components';
+
+This example demonstrates how to declare dependencies between form fields using the `deps` property.
+
+Example explanation: The `City` field will only be displayed when `Country` has a value.
+
+You can also use `form.getValueIn('country')` as an input parameter for the component under the city `Field` to control the component's behavior, such as filtering cities based on the selected country.
+
+<NodeFormDynamicPreview />

+ 15 - 0
apps/docs/src/en/examples/node-form/effect.mdx

@@ -0,0 +1,15 @@
+---
+outline: false
+---
+
+
+# Effect
+
+import { NodeFormEffectPreview } from '../../../../components';
+
+The following examples demonstrate how to configure form side effects. Two examples are provided, with behaviors described below:
+1. Basic effect: When a form field value changes, the current form values will be printed to the console.
+2. Control other fields: When the current form field data changes, it will simultaneously change the value of another form field.
+
+
+<NodeFormEffectPreview />

+ 325 - 311
apps/docs/src/en/guide/advanced/form.mdx

@@ -1,10 +1,21 @@
-# Form
+# Node Form
 
-## Config
+## Terminology
 
-### Enable Node Engine
+<table className="rs-table">
+  <tr>
+    <td>Node Form</td>
+    <td>Specifically refers to forms within flow nodes or forms that expand when clicking nodes, associated with node data.</td>
+  </tr>
+  <tr>
+    <td>Node Engine</td>
+    <td>One of FlowGram.ai's built-in engines, which primarily maintains node data CRUD operations and provides capabilities for rendering, validation, side effects, canvas/variable linkage, etc. Additionally, it provides capabilities for node error capture rendering, placeholder rendering when there's no content, as shown in the following chapter examples.</td>
+  </tr>
+</table>
+
+## Quick Start
 
-Node engine is required to use form
+### Enable Node Engine
 
 [> API Detail](https://github.com/bytedance/flowgram.ai/blob/main/packages/client/editor/src/preset/editor-props.ts#L54)
 
@@ -14,16 +25,16 @@ Node engine is required to use form
 {
   nodeEngine: {
     /**
-     * Node engine is required to use form
+     * Node engine must be enabled to use
      */
     enable: true;
     materials: {
       /**
-       * The component to render when the node has an error
+       * Component for rendering node errors
        */
       nodeErrorRender?: NodeErrorRender;
       /**
-       * The component to render when the node has no content
+       * Component for rendering when node has no content
        */
       nodePlaceholderRender?: NodePlaceholderRender;
     }
@@ -31,7 +42,9 @@ Node engine is required to use form
 }
 ```
 
-### Node Configuration formMeta
+### Configure Form
+
+`formMeta` is the only configuration entry point for node forms, configured on each node's NodeRegistry.
 
 [> node-registries.ts](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-fixed-layout-simple/src/node-registries.ts)
 
@@ -42,15 +55,21 @@ export const nodeRegistries: FlowNodeRegistry[] = [
   {
     type: 'start',
     /**
-     * Configure the validation and rendering of the node form
+     * Configure form validation and rendering
      */
     formMeta: {
+      /**
+       * Configure validation to trigger on data change
+       */
       validateTrigger: ValidateTrigger.onChange,
+      /**
+       * Configure validation rules, 'content' is the field path, the following configuration values validate data under this path
+       */
       validate: {
         content: ({ value }) => (value ? undefined : 'Content is required'),
       },
       /**
-       * Render form
+       * Configure form rendering
        */
       render: () => (
        <>
@@ -73,7 +92,9 @@ export const nodeRegistries: FlowNodeRegistry[] = [
 
 ```
 
-### Add form to node rendering
+[> Basic form example](/examples/node-form/basic.html)
+
+### Render Form
 
 [> base-node.tsx](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-fixed-layout-simple/src/components/base-node.tsx)
 
@@ -81,13 +102,13 @@ export const nodeRegistries: FlowNodeRegistry[] = [
 
 export const BaseNode = () => {
   /**
-   * Provide methods related to node rendering
+   * Provides node rendering related methods
    */
   const { form } = useNodeRender()
   return (
     <div className="demo-free-node" className={form?.state.invalid && "error"}>
       {
-        // The form is rendered through formMeta
+        // Form rendering is generated through formMeta
         form?.render()
       }
     </div>
@@ -96,123 +117,101 @@ export const BaseNode = () => {
 
 ```
 
-## Field
+## Core Concepts
+
+### FormMeta
+
+In `NodeRegistry`, we configure node forms through `formMeta`, which follows the following API.
+
+[> FormMeta API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/node/src/types.ts#L89)
+
+It's important to note that node forms differ significantly from general forms in that their data logic (such as validation, side effects after data changes, etc.) needs to remain effective even when the form is not rendered - we call this <span className="rs-red">separation of data and rendering</span>. Therefore, this data logic needs to be configured in non-render fields within formMeta, ensuring the node engine can call these logics even when not rendering. General form engines (like react-hook-form) don't have this restriction, and validation can be written directly in React components.
+
+### FormMeta.render (Rendering)
+The `render` field is used to configure form rendering logic
+
+`render: (props: FormRenderProps<any>) => React.ReactElement;`
 
-[> Demo Detail](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-fixed-layout/src/nodes/start/form-meta.tsx)
+[> FormRenderProps](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/form.ts#L91)
 
-The rendering part of Field, supports three writing methods, as follows:
+The returned React component can use the following form components and models:
 
-:::note `renderProps` parameter
+#### Field (Component)
 
-- field
-- fieldState
-- formState
+`Field` is a React higher-order component for form fields, encapsulating common form field logic such as data and state injection, component refresh, etc. Its core required parameter is `name`, used to declare the form item's path, which must be unique within a form.
 
-:::
+[> Field Props API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L106)
+
+The rendering part of Field supports three writing methods, as follows:
 
 ```tsx pure
 const render = () => (
   <div>
     <Label> 1. Through children </Label>
-    {/* This method is suitable for simple scenarios, Field will inject value onChange, etc. directly into the first layer of children components */}
+    {/* This method is suitable for simple scenarios, Field will directly inject properties like value onChange into the first layer children component */}
     <Field name="c">
       <Input />
     </Field>
-    <Label> 2. Through Render Props  </Label>
-    {/* This method is suitable for complex scenarios, when the returned component has multiple layers of nesting, the user can actively inject the attributes of field into the desired component */}
+    <Label> 2. Through Render Props </Label>
+    {/* This method is suitable for complex scenarios, when the returned component has multiple nested layers, users can actively inject field properties into desired components */}
     <Field name="a">
         {({ field, fieldState, formState }: FieldRenderProps<string>) => <div><Input {...field} /><Feedbacks errors={fieldState.errors}/></div>}
     </Field>
 
-    <Label> 3. Through render function </Label>
-    {/* This method is similar to method 2, but the props are passed in */}
+    <Label> 3. Through passing render function</Label>
+    {/* This method is similar to method 2, but passed through props */}
     <Field name="b" render={({ field }: FieldRenderProps<string>) => <Input {...field} />} />
   </div>
 );
 ```
 
+```ts pure
+interface FieldRenderProps<TValue> {
+  // Field instance
+  field: Field<TValue>;
+  // Field state (reactive)
+  fieldState: Readonly<FieldState>;
+  // Form state
+  formState: Readonly<FormState>;
+}
+```
+[> FieldRenderProps API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L125)
 
-## FiledArray
+#### Field (Model)
 
-The following example shows:
-1. The writing method of the array
-2. How to configure the validation of the array item
-3. How to display the error of the array item
+`Field` instance is usually passed through render props (as in above example), or obtained through `useCurrentField` hook. It contains common APIs for form fields at the rendering level.
+Note: `Field` is a rendering model, only providing APIs that general components need, such as `value` `onChange` `onFocus` `onBlur`. For data-related APIs, please use the `Form` model instance, such as `form.setValueIn(name, value)` to set a field's value.
 
-<div className="rs-center" >
-  <img loading="lazy" src="/field-array.gif"  style={{ maxWidth: 600 }}/>
-</div>
+[> Field Model API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L34)
 
-```tsx pure
-import {
-  Field,
-  FieldArray,
-  FieldArrayRenderProps,
-  FieldRenderProps,
-  FormMeta,
-  FormRenderProps,
-  ValidateTrigger,
-} from '@flowgram.ai/fixed-layout-editor';
-import Label from '@douyinfe/semi-ui/lib/es/form/label';
-import { Button, Input } from '@douyinfe/semi-ui';
-
-import { Feedback } from '../components/feedback';
-
-interface FormData {
-  arr: string[];
-}
+#### FieldArray (Component)
 
-export const renderNodeWithArray = ({ form }: FormRenderProps<FormData>) => (
-  <>
-    <Label> My array </Label>
-    <FieldArray name="arr">
-      {({ field }: FieldArrayRenderProps<string>) => (
-        <>
-          {field.map((child, index) => (
-            <div key={child.key} className="array-item-wrapper">
-              <Field name={child.name}>
-                {({ field: childField, fieldState: childState }: FieldRenderProps<string>) => (
-                  <div>
-                    <Input {...childField} /> <Feedback errors={childState?.errors} />
-                  </div>
-                )}
-              </Field>
-              <Button style={{ marginLeft: 8 }} onClick={() => field.delete(index)}>
-                del
-              </Button>
-            </div>
-          ))}
-          <div>
-            <Button
-              style={{ marginTop: 8, width: 200 }}
-              onClick={() => field.append('default value')}
-            >
-              Add
-            </Button>
-          </div>
-        </>
-      )}
-    </FieldArray>
-  </>
-);
+`FieldArray` is a React higher-order component for array type fields, encapsulating common logic for array type fields, such as data and state injection, component refresh, and array item iteration. Its core required parameter is `name`, used to declare the form item's path, which must be unique within a form.
 
-export const arrayMeta: FormMeta = {
-  render: renderNodeWithArray,
-  validateTrigger: ValidateTrigger.onChange,
-  // The name in the validate map supports fuzzy matching
-  validate: {
-    ['arr.*']: () => 'array item error',
-  },
-};
-```
+Basic usage of `FieldArray` can be found in the following example:
+
+[> Array example](/examples/node-form/array.html)
+
+#### FieldArray (Model)
 
-## Validation
+`FieldArray` inherits from `Field`, it's the rendering level model for array type fields. Besides common rendering level APIs, it also includes basic array operations like `FieldArray.map`, `FieldArray.remove`, `FieldArray.append`, etc. API usage can also be found in the above [array example](/examples/node-form/array.html).
 
-[> Demo Detail](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-fixed-layout/src/form-components/form-inputs/index.tsx#L37)
+[> FieldArray Model API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L69)
 
-### Validation Configuration
+#### Form (Component)
 
-Validation logic is configured globally, and the validation logic is declared by the form item path
+`Form` component is the outermost higher-order component for forms. The above capabilities like `Field` `FieldArray` can only be used under this higher-order component. Node form rendering has already encapsulated `<Form />` inside the engine, so users don't need to care about it and can directly use `Field` in the React component returned by `render`. However, if users need to use the form engine independently or render a form independently outside the node, they need to wrap the form content with the `Form` component themselves.
+
+#### Form (Model)
+
+`Form` instance can be obtained through the input parameters of the `render` function, or through the `useForm` hook, see [example](#useform). It is the core model facade of the form, through which users can manipulate form data, listen to changes, trigger validation, etc.
+
+[> Form Model API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/form.ts#L58)
+
+### Validation
+Based on the "separation of data and rendering" concept mentioned in the [FormMeta](#formmeta) section, validation logic needs to be configured globally in `FormMeta`, and declared through path matching to act on form items, as shown in the following example.
+
+Paths support fuzzy matching, see [Paths](#paths) section.
 
 <div className="rs-center" >
   <img loading="lazy" src="/form-validate.gif"  style={{ maxWidth: 600 }}/>
@@ -221,7 +220,7 @@ Validation logic is configured globally, and the validation logic is declared by
 ```tsx pure
 export const renderValidateExample = ({ form }: FormRenderProps<FormData>) => (
   <>
-    <Label> a (Maximum length is 5)</Label>
+    <Label> a (max length is 5)</Label>
     <Field name="a">
       {({ field: { value, onChange }, fieldState }: FieldRenderProps<string>) => (
         <>
@@ -230,7 +229,7 @@ export const renderValidateExample = ({ form }: FormRenderProps<FormData>) => (
         </>
       )}
     </Field>
-    <Label> b (If a exists, b can be optional) </Label>
+    <Label> b (if a exists, b can be optional) </Label>
     <Field
       name="b"
       render={({ field: { value, onChange }, fieldState }: FieldRenderProps<string>) => (
@@ -248,276 +247,291 @@ export const VALIDATE_EXAMPLE: FormMeta = {
   // Validation timing configuration
   validateTrigger: ValidateTrigger.onChange,
   validate: {
-    // Simply validate the value
-    a: ({ value }) => (value.length > 5 ? 'Maximum length is 5' : undefined),
-    // Validate the value depends on the value of other form items
+    // Simply validate value
+    a: ({ value }) => (value.length > 5 ? 'Max length is 5' : undefined),
+    // Validation depends on other form item values
     b: ({ value, formValues }) => {
       if (formValues.a) {
         return undefined;
       } else {
-        return value ? 'undefined' : 'a exists when b is required';
+        return value ? 'undefined' : 'b is required when a exists';
       }
     },
-    // Validate the value depends on the node or canvas information
+    // Validation depends on node or canvas information
     c: ({ value, formValues, context }) => {
       const { node, playgroundContext } = context;
-      // The logic is omitted here
+      // Logic omitted here
     },
   },
 };
 ```
-### Validation Timing
+
+#### Validation Timing
 
 <table className="rs-table">
   <tr>
     <td>`ValidateTrigger.onChange`</td>
-    <td>Validate when the form data changes (does not include initial data)</td>
+    <td>Validate when form data changes (not including initialization data)</td>
   </tr>
   <tr>
     <td>`ValidateTrigger.onBlur`</td>
-    <td>Validate when the form item input control is onBlur</td>
+    <td>Validate when form item input control onBlur.<br/>Note, there are two prerequisites: first, the form item's input control needs to support the `onBlur` parameter, second, `Field.onBlur` needs to be passed to that control: <br/>```<Field>{({field})=><Input ... onBlur={field.onBlur}>}</Field>```</td>
   </tr>
 </table>
 
-### Path Fuzzy Matching
+It's recommended to configure `validateTrigger` as `ValidateTrigger.onChange` i.e., validate when data changes. If configured as `ValidateTrigger.onBlur`, validation will only trigger when the component blur event triggers. When the node form is not rendering, even if the data changes, validation won't trigger.
 
-The path (key) of the validation configuration supports fuzzy matching, which is usually used in the array scenario, see the following example
+#### Actively Trigger Validation
+
+1. Actively trigger validation for the entire form
+
+```pure tsx
+const form = useForm()
+form.validate()
+```
+
+2. Actively trigger validation for a single form item
+
+```pure tsx
+const validate = useFieldValidate(name)
+validate()
+```
+If `name` is not passed, it defaults to getting the `validate` of the `Field` under the current `<Field />` tag. By passing `name`, you can get any `Field`'s validate under `<Form />`.
+
+### Paths
+
+1. Form paths use `.` as level separator, e.g., `a.b.c` points to `1` under data `{a:{b:{c:1}}}`
+2. Paths support fuzzy matching, used in validation and side effect configuration. As shown in the following example. Usually used more in array scenarios.
 
 <div className="rs-red">
-  Note: * represents only one level of drilling down
+  Note: * only represents drilling down one level
 </div>
 
 <table className="rs-table">
   <tr>
     <td>`arr.*`</td>
-    <td>`arr` field's all first-level sub-items</td>
+    <td>All first-level sub-items of `arr` field</td>
   </tr>
   <tr>
     <td>`arr.x.*`</td>
-    <td>`arr.x` all first-level sub-items</td>
+    <td>All first-level sub-items of `arr.x`</td>
   </tr>
   <tr>
     <td>`arr.*.x`</td>
-    <td>`arr` all first-level sub-items' sub-items `x`</td>
+    <td>`x` under all first-level sub-items of `arr`</td>
   </tr>
 </table>
 
-## Side Effects (effect)
-
-### Side Effects Configuration
-
-The following example shows:
+### Side Effects (effect)
 
-- How to configure the effect of a normal field
-- How to configure the effect of an array field on the following events
-- onValueChange
-- onValueInit
-- onArrayAppend
-- onArrayDelete
-
-<div className="rs-center" >
-  <img loading="lazy" src="/form-effect.gif"  style={{ maxWidth: 600 }}/>
-</div>
-
-```tsx pure
-import * as React from 'react';
-
-import {
-  ArrayAppendEffect,
-  ArrayDeleteEffect,
-  createEffectOptions,
-  DataEvent,
-  Effect,
-  Field,
-  FieldArray,
-  FieldArrayRenderProps,
-  FieldRenderProps,
-  FormMeta,
-  FormRenderProps,
-  ValidateTrigger,
-} from '@flowgram.ai/fixed-layout-editor';
-import Label from '@douyinfe/semi-ui/lib/es/form/label';
-import { Button, Input } from '@douyinfe/semi-ui';
-
-import { Feedback } from '../components/feedback';
-
-interface FormData {
-  a: string;
-  arr: string[];
-}
+Side effects are a concept unique to node forms, referring to side effects that need to be executed when node data changes. Similarly, following the principle of "separation of data and rendering", side effects, like validation, are also configured globally in `FormMeta`.
+- Configured in key-value form, where key represents form item path matching rules (supports fuzzy matching) and value is the effect acting on that path.
+- Value is an array, meaning one form item can have multiple effects.
 
-export const renderEffectExample = ({ form }: FormRenderProps<FormData>) => (
-  <>
-    <Label> a </Label>
-    <Field name="a">
-      {({ field: { value, onChange }, fieldState }: FieldRenderProps<string>) => (
-        <>
-          <Input value={value} onChange={onChange} />
-          <Feedback errors={fieldState?.errors} />
-        </>
-      )}
-    </Field>
-    <Label> My array </Label>
-    <FieldArray name="arr">
-      {({ field }: FieldArrayRenderProps<string>) => (
-        <>
-          {field.map((child, index) => (
-            <div key={child.key} className="array-item-wrapper">
-              <Field name={child.name}>
-                {({ field: childField, fieldState: childState }: FieldRenderProps<string>) => (
-                  <div>
-                    <Input {...childField} /> <Feedback errors={childState?.errors} />
-                  </div>
-                )}
-              </Field>
-              <Button style={{ marginLeft: 8 }} onClick={() => field.delete(index)}>
-                del
-              </Button>
-            </div>
-          ))}
-          <div>
-            <Button
-              style={{ marginTop: 8, width: 200 }}
-              onClick={() => field.append('default value')}
-            >
-              Add
-            </Button>
-          </div>
-        </>
-      )}
-    </FieldArray>
-  </>
-);
+```tsx pur
 
-export const EFFECT_V2: FormMeta = {
-  render: renderEffectExample,
-  // The effect configuration is a map of the form item path to the effect configuration
+export const EFFECT_EXAMPLE: FormMeta = {
+  ...
   effect: {
-    a: [
-      createEffectOptions<Effect>(DataEvent.onValueChange, ({ value, prevValue }) => {
-        console.log(`a changed: current: ${value} prev:${prevValue}`);
-      }),
-    ],
-    arr: [
-      createEffectOptions<ArrayAppendEffect>(DataEvent.onArrayAppend, ({ value, index }) => {
-        console.log(`arr appended: value=${value}, index=${index}`);
-      }),
-      createEffectOptions<ArrayDeleteEffect>(DataEvent.onArrayDelete, ({ index }) => {
-        console.log(`arr deleted: index=${index}`);
-      }),
-    ],
-    ['arr.*']: [
-      createEffectOptions<Effect>(DataEvent.onValueChange, ({ value, prevValue }) => {
-        console.log(`arr item value changed: current: ${value} prev:${prevValue}`);
-      }),
+    ['a.b']: [
+      {
+        event: DataEvent.onValueChange,
+        effect: ({ value }: EffectFuncProps<string, FormData>) => {
+          console.log('a.b value changed:', value);
+        },
+      },
     ],
-  },
+    ['arr.*']:[
+      {
+        event: DataEvent.onValueInit,
+        effect: ({ value, name }: EffectFuncProps<string, FormData>) => {
+          console.log(name + ' value init:', value);
+        },
+      },
+    ]
+  }
 };
 ```
-### Side Effects Event
 
-```ts pure
-export enum DataEvent {
-  /* Triggered when the value is different from the initial value or the previous value */
-  onValueChange = 'onValueChange',
-  /**
-   * Triggered when the initial value is set, the triggering scenarios are:
-   * - The form is configured with defaultValue, and the form is initialized
-   * - A form item is configured with defaultValue, and the form item is initialized without a value
-   */
-  onValueInit = 'onValueInit',
-  /**
-   * Triggered when the initial value is set, the triggering scenarios are:
-   * - The form is configured with defaultValue, and the form is initialized
-   * - A form item is configured with defaultValue, and the form item is initialized without a value
-   */
-  onValueInitOrChange = 'onValueInitOrChange',
-  /* Not recommended, this event depends on FieldArray rendering, and the value event may not be triggered in the case of non-rendering */
-  onArrayAppend = 'onArrayAppend',
-  /* Not recommended, this event depends on FieldArray rendering, and the value event may not be triggered in the case of non-rendering */
-  onArrayDelete = 'onArrayDelete',
+```tsx pur
+interface EffectFuncProps<TFieldValue = any, TFormValues = any> {
+  name: FieldName;
+  value: TFieldValue;
+  prevValue?: TFieldValue;
+  formValues: TFormValues;
+  form: IForm;
+  context: NodeContext;
 }
 ```
 
-### API
+[Effect Related API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/node/src/types.ts#L54)
 
-```ts pure
-// The effect of onValueChange and onValueInit follows this interface
-export type Effect<TFieldValue = any, TFormValues = any> = (props: {
-  value?: TFieldValue;
-  prevValue?: TFieldValue;
-  formValues?: TFormValues;
-  context?: NodeContext;
-}) => void;
-
-export type ArrayAppendEffect<TFieldValue = any, TFormValues = any> = (props: {
-  index?: number;
-  value?: TFieldValue;
-  arrayValues?: Array<TFieldValue>;
-  formValues?: TFormValues;
-  context?: NodeContext;
-}) => void;
-
-export type ArrayDeleteEffect<TFieldValue = any, TFormValues = any> = (props: {
-  index: number;
-  arrayValue?: Array<TFieldValue>;
-  formValues?: TFormValues;
-  context?: NodeContext;
-}) => void;
+#### Side Effect Timing
+
+<table className="rs-table">
+  <tr>
+    <td>`DataEvent.onValueChange`</td>
+    <td>Triggered when data changes</td>
+  </tr>
+  <tr>
+    <td>`DataEvent.onValueInit`</td>
+    <td>Triggered when data initializes</td>
+  </tr>
+  <tr>
+    <td>`DataEvent.onValueInitOrChange`</td>
+    <td>Triggered both during data initialization and changes</td>
+  </tr>
+</table>
+
+### Dynamic Field
+
+[> Dynamic Field example](/examples/node-form/dynamic.html)
+
+## Hooks
+
+### Inside Node Form
+The following hooks can be used inside node forms
+
+#### useCurrentField
+`() => Field`
+
+This hook needs to be used inside Field tags
+
+```tsx pur
+const field = useCurrentField()
 ```
+[> Field Model API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L34)
 
-## Dynamic Dependencies
+#### useCurrentFieldState
+`() => FieldState`
 
-Declare dependencies through deps
+This hook needs to be used inside Field tags
 
-```tsx pure
-import * as React from 'react';
-
-import {
-  Field,
-  FieldRenderProps,
-  FormMeta,
-  FormRenderProps,
-} from '@flowgram.ai/fixed-layout-editor';
-import Label from '@douyinfe/semi-ui/lib/es/form/label';
-import { Input, Switch } from '@douyinfe/semi-ui';
-
-interface FormData {
-  isBatch: boolean;
-  batch: string;
+```tsx pur
+const fieldState = useCurrentFieldState()
+```
+[> FieldState API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L158)
+
+#### useFieldValidate
+`(name?: FieldName) => () => Promise<void>`
+
+If you need to actively trigger field validation, you can use this hook to get the Field's validate function.
+
+`name` is the Field's path, if not passed it defaults to getting the validate of the current `<Field />`
+
+```tsx pur
+const validate = useFieldValidate()
+validate()
+```
+
+#### useForm
+`() => Form`
+
+Used to get Form instance.
+
+Note, this hook doesn't work in the first layer of the `render` function, it can only be used inside React components within the `render` function. The `render` function's input parameters already include `form: Form`, which can be used directly.
+
+1. Directly use `props.form` in render function's first layer
+```tsx pur
+const formMeta = {
+  render: ({form}) =>
+  <div>
+    {form.getValueIn('my.path')}
+  </div>
 }
+```
 
-export const renderDynamicExample = ({ form }: FormRenderProps<FormData>) => (
-  <>
+2. Can use `useForm` inside components
+
+```tsx pur
+
+const formMeta = {
+  render: () =>
     <div>
-      <Label> is Batch ? </Label>
-      <Field name="isBatch">
-        {({ field: { value, onChange } }: FieldRenderProps<boolean>) => (
-          <>
-            <Switch checked={value} onChange={onChange} size={'small'} />
-          </>
-        )}
+      <Field name={'my.path'}>
+        <MySelect />
       </Field>
     </div>
-    <Field
-      name="batch"
-      render={({ field }: FieldRenderProps<string>) =>
-        form.values?.isBatch ? (
-          <>
-            <Label> batch </Label>
-            <Input {...field} />
-          </>
-        ) : (
-          <></>
-        )
-      }
-      // Configure the dependencies of the form item through deps
-      deps={['isBatch']}
-    />
-  </>
-);
+}
 
-export const DYNAMIC_V2: FormMeta = {
-  render: renderDynamicExample,
-};
+// MySelect.tsx
+...
+const form = useForm()
+const valueNeeded = form.getValueIn('my.other.path')
+...
+```
+
+<span className="rs-red">Note: Form's api doesn't have any reactive capabilities, if you need to monitor a field's value, use [useWatch](#usewatch)</span>
+
+#### useWatch
+`<TValue = FieldValue>(name: FieldName) => TValue`
+
+This hook is similar to the above `useForm`, it doesn't work in the first layer of the `render` function, only usable inside wrapped components. If you need to use it at the `render` root level, you can wrap the returned content in a component layer.
+
+```tsx pur
+{
+  render: () =>
+    <div>
+      <Field name={'a'}><A /></Field>
+      <Field name={'b'}><B /></Field>
+    </div>
+}
+
+// A.tsx
+...
+const b = useWatch('b')
+// do something with b
+...
+```
+
+### Outside Node Form
+The following hooks are used outside node forms, such as on the canvas globally or on adjacent nodes to monitor a node form's data or state. Usually needs to pass `node: FlowNodeEntity` as a parameter
+
+#### useWatchFormValues
+Monitor the values of the entire form inside the node
+
+`<TFormValues = any>(node: FlowNodeEntity) => TFormValues | undefined`
+
+```tsx pur
+const values = useWatchFormValues(node)
+```
+
+#### useWatchFormValueIn
+
+Monitor the value of a specific form item inside the node
+
+`<TValue = any>(node: FlowNodeEntity,name: string) => TFormValues | undefined`
+
+```tsx pur
+const value = useWatchFormValueIn(node, name)
+```
+
+#### useWatchFormState
+
+Monitor the form state inside the node
+
+`(node: FlowNodeEntity) => FormState | undefined`
+
+```tsx pur
+const formState = useWatchFormState(node)
+```
+
+#### useWatchFormErrors
+
+Monitor the form Errors inside the node
+
+`(node: FlowNodeEntity) => Errors | undefined`
+
+```tsx pur
+const errors = useWatchFormErrors(node)
+```
+
+#### useWatchFormWarnings
+
+Monitor the form Warnings inside the node
+
+`(node: FlowNodeEntity) => Warnings | undefined`
+
+```tsx pur
+const warnings = useWatchFormErrors(node)
 ```

+ 6 - 0
apps/docs/src/zh/examples/node-form/array.mdx

@@ -7,4 +7,10 @@ outline: false
 
 import { NodeFormArrayPreview } from '../../../../components/node-form/array/preview';
 
+以下例子展示了数组的基本用法,包含:
+- 基本写法(渲染、增删)。
+- 如何对数组每项配置校验逻辑。 此处的校验规则为每项最大长度不超过8个英文字符。
+- 如何对数组每项配置副作用。 此处的副作用为每项在初始化时控制台输出 `${name} value init to ${value}`, 值变更时输出 `${name} value changed to ${value}`
+- 数组项如何做交换。
+
 <NodeFormArrayPreview />

+ 5 - 1
apps/docs/src/zh/examples/node-form/effect.mdx

@@ -3,8 +3,12 @@ outline: false
 ---
 
 
-# 表单项变更副作用 ( effect)
+# 副作用
 
 import { NodeFormEffectPreview } from '../../../../components';
 
+以下例子展示了表单副作用的配置方式。举了两个个例子,行为描述如下
+1. Basic effect(基础例子):当表单项值变更时,控制台会打印表单当前值。
+2. Control other fields (控制其他表单项的值):当前表单项数据变更时要同时改变另一个表单项的值。
+
 <NodeFormEffectPreview />

+ 188 - 18
apps/docs/src/zh/guide/advanced/form.mdx

@@ -1,11 +1,22 @@
-# 表单的使用
+# 节点表单
+
+## 术语
+
+<table className="rs-table">
+  <tr>
+    <td>节点表单</td>
+    <td>特指流程节点内的表单或点击节点展开的表单,关联节点数据。</td>
+  </tr>
+  <tr>
+    <td>节点引擎</td>
+    <td>FlowGram.ai 内置的引擎之一,它核心维护了节点数据的增删查改,并提供渲染、校验、副作用、画布或变量联动等能力, 除此之外,它还提供节点错误捕获渲染、无内容时的 placeholder 渲染等能力,见以下章节例子。</td>
+  </tr>
+</table>
 
 ## 快速开始
 
 ### 开启节点引擎
 
-需要开启节点引擎才能使用
-
 [> API Detail](https://github.com/bytedance/flowgram.ai/blob/main/packages/client/editor/src/preset/editor-props.ts#L54)
 
 ```tsx pure title="use-editor-props.ts" {3}
@@ -31,7 +42,9 @@
 }
 ```
 
-### 配置 formMeta
+### 配置表单
+
+ `formMeta` 是节点表单唯一配置入口,配置在每个节点的NodeRegistry 上。
 
 [> node-registries.ts](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-fixed-layout-simple/src/node-registries.ts)
 
@@ -50,7 +63,7 @@ export const nodeRegistries: FlowNodeRegistry[] = [
        */
       validateTrigger: ValidateTrigger.onChange,
       /**
-       * 配置校验规则, 'content' 规则生效的字段路径
+       * 配置校验规则, 'content' 为字段路径,以下配置值对该路径下的数据进行校验。
        */
       validate: {
         content: ({ value }) => (value ? undefined : 'Content is required'),
@@ -79,7 +92,9 @@ export const nodeRegistries: FlowNodeRegistry[] = [
 
 ```
 
-### 添加表单到节点
+[> 表单写法的基础例子](/examples/node-form/basic.html)
+
+### 渲染表单
 
 [> base-node.tsx](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-fixed-layout-simple/src/components/base-node.tsx)
 
@@ -101,6 +116,7 @@ export const BaseNode = () => {
 };
 
 ```
+
 ## 核心概念
 
 ### FormMeta
@@ -110,7 +126,7 @@ export const BaseNode = () => {
 [> FormMeta API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/node/src/types.ts#L89)
 
 这里特别说明, 节点表单与通用表单有一个很大的区别,它的数据逻辑(如校验、数据变更后的副作用等)需要在表单不渲染的情况下依然生效,我们称 <span className="rs-red">数据与渲染分离</span>
-。所以这些数据逻辑需要配置在formMeta 中的非render 字段中,保证不渲染情况下节点引擎也可以调用 到这些逻辑。而通用表单引擎(如react-hook-form)则没有这个限制, 校验可以直接写在react组件中。
+。所以这些数据逻辑需要配置在formMeta 中的非render 字段中,保证不渲染情况下节点引擎也可以调用到这些逻辑, 而通用表单引擎(如react-hook-form)则没有这个限制, 校验可以直接写在react组件中。
 
 
 ### FormMeta.render (渲染)
@@ -121,6 +137,7 @@ export const BaseNode = () => {
 [> FormRenderProps](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/form.ts#L91)
 
 
+返回的 react 组件可使用以下表单组件和模型:
 
 #### Field (组件)
 
@@ -166,7 +183,7 @@ interface FieldRenderProps<TValue> {
 #### Field (模型)
 
 `Field` 实例通常通过render props 传入(如上例子),或通过 `useCurrentField` hook 获取。它包含表单字段在渲染层面的常见API。
-注意: `Field` 是一个渲染模型,如果是数据相关的API 请使用 `Form` 模型。
+注意: `Field` 是一个渲染模型,仅提供一般组件需要的API, 如 `value` `onChange` `onFocus` `onBlur`,如果是数据相关的API 请使用 `Form` 模型实例,如 `form.setValueIn(name, value)` 设置某字段的值
 
 [> Field 模型 API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L34)
 
@@ -187,16 +204,17 @@ interface FieldRenderProps<TValue> {
 
 #### Form(组件)
 
-`Form` 组件是使用表单的最外层高阶组件,上述 `Field` `FieldArray` 等能力仅在该高阶组件下可以使用。节点表单的渲染已经将`<Form />` 封装了,所以用户无需关注,可以直接在`render` 配置中使用 `Field` 即可。但如果用户需要独立使用表单引擎,或者在节点之外独立再渲染一次表单,需要自行在表单内容外包上`Form`组件
+`Form` 组件是表单的最外层高阶组件,上述 `Field` `FieldArray` 等能力仅在该高阶组件下可以使用。节点表单的渲染已经将`<Form />` 封装引擎内部,所以用户无需关注,可以直接在`render` 返回的 react 组件中直接使用 `Field`。但如果用户需要独立使用表单引擎,或者在节点之外独立再渲染一次表单,需要自行在表单内容外包上`Form`组件
 
 #### Form(模型)
 
-`Form` 实例通过`render` 函数的入参获得, 也可通过 hook `useForm` 获取。它是表单核心模型门面,用户可以通过Form 实例操作表单数据、监听变更、触发校验等。
+`Form` 实例通过`render` 函数的入参获得, 也可通过 hook `useForm` 获取,见[例子](#useform)。它是表单核心模型门面,用户可以通过Form 实例操作表单数据、监听变更、触发校验等。
 
 [> Form 模型 API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/form.ts#L58)
 
 ### 校验
-基于[FormMeta]章节中提到的"数据与渲染分离"概念,校验逻辑需配置在 `FormMeta` 全局。且通过表单项路径声明校验逻辑。如下例子
+基于[FormMeta](#formmeta)章节中提到的"数据与渲染分离"概念,校验逻辑需配置在 `FormMeta` 全局, 并通过路径匹配方式声明校验逻辑所作用的表单项,如下例子。
+
 路径支持模糊匹配,见[路径](#路径)章节。
 
 <div className="rs-center" >
@@ -261,7 +279,7 @@ export const VALIDATE_EXAMPLE: FormMeta = {
   </tr>
   <tr>
     <td>`ValidateTrigger.onBlur`</td>
-    <td>表单项输入控件onBlur时校验</td>
+    <td>表单项输入控件onBlur时校验。<br/>注意,这里有两个前提:一是表单项的输入控件需要支持 `onBlur` 入参,二是要将 `Field.onBlur` 传入该控件: <br/>```<Field>{({field})=><Input ... onBlur={field.onBlur}>}</Field>```</td>
   </tr>
 </table>
 `validateTrigger` 建议配置 `ValidateTrigger.onChange` 即数据变更时校验,如果配置 `ValidateTrigger.onBlur`, 校验只会在组件blur事件触发时触发。那么当节点表单不渲染的情况下,就算是数据变更了,也不会触发校验。
@@ -297,23 +315,23 @@ validate()
 <table className="rs-table">
   <tr>
     <td>`arr.*`</td>
-    <td>`arr` 字段的所有一级子项</td>
+    <td>`arr` 字段的所有一级子项</td>
   </tr>
   <tr>
     <td>`arr.x.*`</td>
-    <td>`arr.x` 的所有一级子项</td>
+    <td>`arr.x` 的所有一级子项</td>
   </tr>
   <tr>
     <td>`arr.*.x`</td>
-    <td>`arr` 下的所有一级子项下的子项 `x`</td>
+    <td>`arr` 所有一级子项下的 `x`</td>
   </tr>
 </table>
 
 ### 副作用 (effect)
 
-副作用是节点表单特有的概念,指在节点数据发生变更时需要执行的副作用。同样遵循 "数据与渲染分离" 的原则,副作用和校验相似,也配置在 `FormMeta` 全局。
-- 通过key value 形式配置,key 标识表单项路径,支持模糊匹配,value 为作用在该路径上的effect
-- value 为数组,即支持一个表单项有多个effect
+副作用是节点表单特有的概念,指在节点数据发生变更时需要执行的副作用。同样遵循 "数据与渲染分离" 的原则,副作用和校验相似,也配置在 `FormMeta` 全局。
+- 通过 key value 形式配置,key 表示表单项路径匹配规则,支持模糊匹配,value 为作用在该路径上的effect
+- value 为数组,即支持一个表单项有多个effect
 
 
 ```tsx pur
@@ -376,3 +394,155 @@ interface EffectFuncProps<TFieldValue = any, TFormValues = any> {
 [> 联动例子](/examples/node-form/dynamic.html)
 
 
+## hooks
+### 节点表单内
+以下hook 可在节点表单内部使用
+
+#### useCurrentField
+`() => Field`
+
+该 hook 需要在Field 标签内部使用
+
+```tsx pur
+const field = useCurrentField()
+```
+[> Field 模型 API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L34)
+
+
+#### useCurrentFieldState
+`() => FieldState`
+
+该 hook 需要在Field 标签内部使用
+
+```tsx pur
+const fieldState = useCurrentFieldState()
+```
+[> FieldState API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L158)
+
+#### useFieldValidate
+`(name?: FieldName) => () => Promise<void>`
+
+如果需要主动触发字段的校验,可以使用该hook 获取到 Field 的 validate 函数。
+
+`name` 为 Field 的路径,不传则默认获取当前 `<Field />` 下的validate
+
+```tsx pur
+const validate = useFieldValidate()
+validate()
+```
+
+#### useForm
+`() => Form`
+
+用于获取 Form 实例。
+
+注意,该hook 在 `render` 函数第一层不生效,仅在 `render` 函数内的 react 组件内部才可使用。`render` 函数的入参中已经传入了 `form: Form`, 可以直接使用。
+
+1. 在 render 函数第一层直接使用 `props.form`
+```tsx pur
+const formMeta = {
+  render: ({form}) =>
+  <div>
+    {form.getValueIn('my.path')}
+  </div>
+}
+```
+
+2. 在组件内部可使用 `useForm`
+
+```tsx pur
+
+const formMeta = {
+  render: () =>
+    <div>
+      <Field name={'my.path'}>
+        <MySelect />
+      </Field>
+    </div>
+}
+
+// MySelect.tsx
+...
+const form = useForm()
+const valueNeeded = form.getValueIn('my.other.path')
+...
+```
+
+<span className="rs-red">注意:Form 的 api 不具备任何响应式能力,若需监听某字段值,可使用 [useWatch](#usewatch) </span>
+
+
+#### useWatch
+`<TValue = FieldValue>(name: FieldName) => TValue`
+
+该 hook 和上述 `useForm` 相似, 在 `render` 函数返回组件的第一层不生效,仅在封装过的组件内部可用。如果需要在 `render` 根级别使用,可以对 `render` 返回的内容做一层组件封装。
+
+```tsx pur
+{
+  render: () =>
+    <div>
+      <Field name={'a'}><A /></Field>
+      <Field name={'b'}><B /></Field>
+    </div>
+}
+
+// A.tsx
+...
+const b = useWatch('b')
+// do something with b
+...
+```
+
+
+### 节点表单外
+以下 hook 用于在节点表单外部,如画布全局、相邻节点上需要去监听某个节点表单的数据或状态。通常需要传入 `node: FlowNodeEntity` 作为参数
+
+#### useWatchFormValues
+监听 node 内整个表单的值
+
+`<TFormValues = any>(node: FlowNodeEntity) => TFormValues | undefined`
+
+```tsx pur
+const values = useWatchFormValues(node)
+```
+
+#### useWatchFormValueIn
+
+监听 node 内某个表单项的值
+
+`<TValue = any>(node: FlowNodeEntity,name: string) => TFormValues | undefined`
+
+```tsx pur
+const value = useWatchFormValueIn(node, name)
+```
+
+#### useWatchFormState
+
+监听 node 内表单的状态
+
+`(node: FlowNodeEntity) => FormState | undefined`
+
+```tsx pur
+const formState = useWatchFormState(node)
+```
+
+#### useWatchFormErrors
+
+监听 node 内表单的 Errors
+
+`(node: FlowNodeEntity) => Errors | undefined`
+
+```tsx pur
+const errors = useWatchFormErrors(node)
+```
+
+#### useWatchFormWarnings
+
+监听 node 内表单的 Warnings
+
+`(node: FlowNodeEntity) => Warnings | undefined`
+
+```tsx pur
+const warnings = useWatchFormErrors(node)
+```
+
+