Procházet zdrojové kódy

docs(variable): detail output variable (#927)

* docs: fix nav match

* docs(variable): detail output variable
Yiwei Mao před 2 měsíci
rodič
revize
47324c0782

+ 2 - 2
apps/docs/src/en/_nav.json

@@ -2,12 +2,12 @@
   {
     "text": "Guide",
     "link": "/guide/introduction",
-    "activeMatch": "/guide/introduction"
+    "activeMatch": "/guide/"
   },
   {
     "text": "Materials",
     "link": "/materials/introduction",
-    "activeMatch": "/materials/introduction"
+    "activeMatch": "/materials/"
   },
   {
     "text": "Examples",

+ 16 - 34
apps/docs/src/en/guide/variable/variable-consume.mdx

@@ -1,14 +1,19 @@
 # Consuming Variables
 
-In Flowgram, managing and consuming variables is at the core of building dynamic, interactive applications. Understanding how to effectively consume variables is crucial for developers. This document will guide you through the various ways to consume variables in different scenarios.
+In Flowgram, managing and consuming variables is at the core of building dynamic, interactive applications.
+
+## Official Material: `VariableSelector`
+
+To make it easier for you to integrate variable selection functionality into your application, the official materials provide the `VariableSelector` component. For details, please read the documentation: [VariableSelector](/materials/components/variable-selector)
+
 
 ## Getting the Variable Tree within a Node
 
-In the nodes on the canvas, we often need to get the variables available in the current scope and display them in a tree structure for users to select and operate. The `useAvailableVariables` React Hook is designed for this purpose.
+In the nodes on the canvas, we often need to get the variables available in the current scope and display them in a tree structure for users to select and operate.
 
 ### `useAvailableVariables`
 
-`useAvailableVariables` is a lightweight Hook that directly returns an array of available variables (`VariableDeclaration[]`) in the current scope. If you only need a simple list of variables and don't need to perform more complex operations, `useAvailableVariables` is a more convenient choice.
+`useAvailableVariables` is a lightweight Hook that directly returns an array of available variables (`VariableDeclaration[]`) in the current scope.
 
 ```tsx pure title="use-variable-tree.tsx"
 import {
@@ -88,39 +93,14 @@ const renderVariable = (variable: BaseVariableField) => ({
 
 ```
 
-## Official Material: `VariableSelector`
-
-To make it easier for you to integrate variable selection functionality into your application, we have prepared an official material for you - the `VariableSelector` component. It encapsulates all the logic mentioned earlier, allowing you to have a powerful and beautiful variable selector without starting from scratch.
-
-<img loading="lazy" src="/materials/variable-selector.png" style={{width:500}}/>
-
-`VariableSelector` not only supports displaying a variable tree but also has built-in advanced features such as search and filtering, which can greatly enhance the user experience. For a more detailed introduction, please refer to the [Official Form Materials](/guide/form/form-materials.html) documentation.
 
-You can use it in the following two ways:
+## `ScopeAvailableData`
 
-**1. By Referencing the NPM Package**
-
-This is the simplest and most direct way. With just one line of code, you can introduce `VariableSelector` into your project.
-
-```tsx
-import { VariableSelector } from '@flowgram.ai/form-materials';
-```
-
-**2. By Copying the Source Code via CLI**
-
-If you want to customize `VariableSelector` more deeply, we also provide a way to copy the component's source code directly into your project via the CLI. This way, you can modify it as you wish to meet your unique business needs.
-
-```bash
-npx @flowgram.ai/materials components/variable-selector
-```
-
-## `ScopeAvailableData`: Your Variable Toolbox
-
-The `ScopeAvailableData` object is one of the core components of the variable system. It is returned by the `useScopeAvailable` Hook and is your main bridge for interacting with the available variables in the scope. You can think of it as a powerful "variable toolbox."
+The `ScopeAvailableData` object is one of the core components of the variable system. It is returned by the `useScopeAvailable` Hook and is your main bridge for interacting with the **available variables in the scope**.
 
 ### `useScopeAvailable`
 
-`useScopeAvailable` is a more powerful Hook that returns a `ScopeAvailableData` object, which not only contains all the available variable information in the current scope but also provides some advanced APIs, such as `trackByKeyPath`.
+`useScopeAvailable` returns a `ScopeAvailableData` object, which not only contains all the available variable information in the current scope but also provides some advanced APIs, such as `trackByKeyPath`.
 
 Its main differences from `useAvailableVariables` are:
 
@@ -160,7 +140,7 @@ function MyComponent() {
 }
 ```
 
-### Tracking Changes to a Single Variable: `trackByKeyPath`
+### `trackByKeyPath`
 
 When you are only concerned with the changes of a specific variable (especially one nested in an Object or Array), `trackByKeyPath` comes in handy. It allows you to accurately "subscribe" to the updates of this variable without causing the component to re-render due to changes in other unrelated variables, thus achieving finer performance optimization.
 
@@ -193,9 +173,11 @@ function UserNameDisplay() {
 }
 ```
 
-### Advanced Event Listening API
+### Global Listening API
+
+In addition to `trackByKeyPath`, `ScopeAvailableData` also provides a set of global variable change event listening APIs that allow you to control the response logic to variable changes more finely.
 
-In addition to `trackByKeyPath`, `ScopeAvailableData` also provides a set of lower-level event listening APIs that allow you to control the response logic to variable changes more finely. This is very useful when dealing with complex scenarios that require manual subscription management.
+This is very useful when dealing with complex scenarios that require manual subscription management.
 
 Let's use a table to compare these three core listening APIs in detail:
 

+ 335 - 106
apps/docs/src/en/guide/variable/variable-output.mdx

@@ -1,28 +1,26 @@
 # Outputting Variables
 
-In Flowgram, variables are the key hubs that connect various nodes. The output of one node is often the input of another.
+We mainly divide output variables into three categories:
 
-We mainly divide variables into two categories:
-
-- **Node Variables**: The scope is limited to a single node, usually as the output of that node for subsequent nodes to use.
-- **Global Variables**: The scope runs through the entire process, and any node can read and modify it. It is suitable for storing some public states or configurations.
+1. **Output Node Variables**: Usually as the output of that node for subsequent nodes to use.
+2. **Output Node Private Variables**: Output variables are limited to the inside of the node (including child nodes) and cannot be accessed by external nodes.
+3. **Output Global Variables**: Runs through the entire process, and any node can read it. It is suitable for storing some public states or configurations.
 
 ## Outputting Node Variables
 
-Outputting a node variable means that this variable is bound to the life cycle of the current node. When the node is created, the variable is born; when the node is deleted, the variable also disappears. We usually have two ways to output node variables:
-
-1.  **Through form configuration**: In the node's setting panel (Form), declare output variables through some preset configurations or custom logic. This method is very intuitive, what you see is what you get.
-2.  **Through API calls**: In the plugin (Plugin), dynamically add, modify or delete variables for the node by calling the `node.scope` API. This method is more flexible and suitable for handling complex and dynamic business scenarios.
+Outputting a node variable means that this variable is bound to the life cycle of the current node. When the node is created, the variable is born; when the node is deleted, the variable also disappears.
 
-Next, we will explore the specific usage of these two methods in depth.
+We usually have three ways to output node variables:
 
-### Method 1: Output through Form Configuration
+### Method 1: Sync through Form Side Effects
 
-Configuring in the node's `form-meta.ts` file is the most common way to define node output variables. It can be divided into two ways: one is the "lazy version", using our built-in materials; the other is the "master version", completely customized.
+[Form Side Effects](/guide/form/form#副作用-effect) are usually configured in the node's `form-meta.ts` file and are the most common way to define node output variables.
 
 #### `provideJsonSchemaOutputs` Material
 
-If the structure of the variable that your node needs to output happens to be consistent with the `JSON Schema` structure of the form, then congratulations, the `provideJsonSchemaOutputs` side effect (Effect) material is tailor-made for you! It is like an automated "variable porter", which can automatically convert the json schema in the form into the output variable of the node as it is.
+If the structure of the output variable required by the node matches the [JSON Schema](https://json-schema.org/) structure, you can use the `provideJsonSchemaOutputs` side effect (Effect) material.
+
+It can automatically convert the JSON Schema of the form into the output variable of the node without additional configuration.
 
 It is very simple to use, just add two lines of configuration in the `effect` of `formMeta`:
 
@@ -40,19 +38,25 @@ export const formMeta = {
 };
 ```
 
-#### Custom Output through Form Side Effects
+#### Custom Output via `createEffectFromVariableProvider`
 
 Although `provideJsonSchemaOutputs` is convenient, it only adapts to JsonSchema.
 
 If you want to define your own set of Schema, then you need to customize the side effects of the form.
 
-Flowgram provides a powerful auxiliary function `createEffectFromVariableProvider`, which can help you easily create a side effect for providing variables. You can think of it as a "variable processing factory", the input is the form data, and the output is the variable you have carefully processed.
+Flowgram provides a powerful helper function `createEffectFromVariableProvider`
+
+You only need to define a `parse` function to customize your own variable synchronization logic:
+- The `parse` function will be called when the form value is initialized and updated
+- The input of the `parse` function is the value of the current field's form
+- The output of the `parse` function is the variable AST information
+
+<br />
 
-In the following example, we create output variables for the two fields of the form `path.to.value` and `path.to.value2`. When the user fills in these two fields in the form, the `parse` function will be triggered to convert the user's input value (`v`) into a standard variable declaration object.
+In the following example, we create output variables for the two fields of the form `path.to.value` and `path.to.value2`:
 
-```tsx pure title="node-registries.ts"
+```tsx pure title="form-meta.ts"
 import {
-  FlowNodeRegistry,
   createEffectFromVariableProvider,
   ASTFactory,
   type ASTNodeJSON
@@ -74,71 +78,67 @@ export function createTypeFromValue(value: string): ASTNodeJSON | undefined {
   }
 }
 
-export const nodeRegistries: FlowNodeRegistry[] = [
-  {
-    type: 'start',
-    formMeta: {
-      effect: {
-        // Create first variable
-        // = variableData.setVar('path.to.value', ASTFactory.createVariableDeclaration(parse(v)))
-        'path.to.value': createEffectFromVariableProvider({
-          // parse form value to variable
-          parse(v: string) {
-            return {
-              meta: {
-                title: `Your Output Variable Title`,
-              },
-              key: `your_variable_global_unique_key_${node.id}`,
-              type: createTypeFromValue(v)
-            }
-          }
-        }),
-        // Create second variable
-        // = variableData.setVar('path.to.value2', ASTFactory.createVariableDeclaration(parse(v)))
-        'path.to.value2': createEffectFromVariableProvider({
-          // parse form value to variable
-          parse(v: string) {
-            return {
-              meta: {
-                title: `Your Output Variable Title 2`,
-              },
-              key: `your_variable_global_unique_key_${node.id}_2`,
-              type: createTypeFromValue(v)
-            }
-          }
-        }),
-      },
-      render: () => (
-       // ...
-      )
-    },
-  }
-]
-
+export const formMeta =  {
+  effect: {
+    // Create first variable
+    // = node.scope.setVar('path.to.value', ASTFactory.createVariableDeclaration(parse(v)))
+    'path.to.value': createEffectFromVariableProvider({
+      // parse form value to variable
+      parse(v: string) {
+        return {
+          meta: {
+            title: `Your Output Variable Title`,
+          },
+          key: `uid_${node.id}`,
+          type: createTypeFromValue(v)
+        }
+      }
+    }),
+    // Create second variable
+    // = node.scope.setVar('path.to.value2', ASTFactory.createVariableDeclaration(parse(v)))
+    'path.to.value2': createEffectFromVariableProvider({
+      // parse form value to variable
+      parse(v: string) {
+        return {
+          meta: {
+            title: `Your Output Variable Title 2`,
+          },
+          key: `uid_${node.id}_2`,
+          type: createTypeFromValue(v)
+        }
+      }
+    }),
+  },
+  render: () => (
+    // ...
+  )
+}
 ```
 
 #### Syncing multiple form fields to one variable
 
-If multiple fields need to be synchronized to a single variable, you need to use the `namespace` field of `createEffectFromVariableProvider` to synchronize the variable data of multiple fields to the same namespace.
-
+If multiple fields are synchronized to one variable, you need to use the `namespace` field of `createEffectFromVariableProvider` to synchronize the variable data of multiple fields to the same namespace.
 
 ```tsx pure title="form-meta.ts"
+import {
+  createEffectFromVariableProvider,
+  ASTFactory,
+} from '@flowgram.ai/fixed-layout-editor';
 
 /**
- * Get information of multiple fields from formValues
+ * Get information of multiple fields from the form
  */
 const variableSyncEffect = createEffectFromVariableProvider({
-
   // Must be added to ensure that the side effects of different fields are synchronized to the same namespace
   namespace: 'your_namespace',
 
   // Parse the form value into a variable
-  parse(_, { form }) {
+  parse(_, { form, node }) {
     return {
       meta: {
         title: `Your Output Variable Title`,
       },
-      key: `your_variable_global_unique_key_${node.id}`,
+      key: `uid_${node.id}`,
       type: createTypeFromValue({
         value1: form.getValueIn('path.to.value'),
         value2: form.getValueIn('path.to.value2'),
@@ -147,28 +147,20 @@ const variableSyncEffect = createEffectFromVariableProvider({
   }
 })
 
-export const nodeRegistries: FlowNodeRegistry[] = [
-  {
-    type: 'start',
-    formMeta: {
-      effect: {
-        'path.to.value': variableSyncEffect,
-        'path.to.value2': variableSyncEffect,
-      },
-      render: () => (
-       // ...
-      )
-    },
-  }
-]
-
+export const formMeta = {
+  effect: {
+    'path.to.value': variableSyncEffect,
+    'path.to.value2': variableSyncEffect,
+  },
+  render: () => (
+   // ...
+  )
+}
 ```
 
-### Method 2: Use the `node.scope` API
-
-In addition to static configuration in the form, we can also get the scope in the plugin (Plugin) through the `node.scope` API to dynamically operate the variables of the node.
+#### Using the `node.scope` API in Side Effects
 
-This method gives you a high degree of freedom. You can add, delete, modify, and query the variables of the node at any time and any place.
+If `createEffectFromVariableProvider` does not meet your needs, you can also directly use the `node.scope` API in form side effects for more flexible and variable operations.
 
 `node.scope` will return a node's variable scope (Scope) object, which has several core methods:
 
@@ -179,7 +171,67 @@ This method gives you a high degree of freedom. You can add, delete, modify, and
 - `clearVar()`: Clear all variables.
 - `clearVar(namespace)`: Clear the variables under the specified namespace.
 
-The following example demonstrates how to get the `Scope` of the start node and perform a series of operations on its variables in the `onInit` life cycle of the plugin.
+
+```tsx pure title="form-meta.tsx"
+import { Effect } from '@flowgram.ai/editor';
+
+export const formMeta = {
+  effect: {
+    'path.to.value': [{
+      event: DataEvent.onValueInitOrChange,
+      effect: ((params) => {
+        const { context, value } = params;
+
+        context.node.scope.setVar(
+          ASTFactory.createVariableDeclaration({
+            meta: {
+              title: `Your Output Variable Title`,
+            },
+            key: `uid_${node.id}`,
+            type: createTypeFromValue(value),
+          })
+        )
+
+        console.log("View generated variables", context.node.scope.getVar())
+
+      }) as Effect,
+    }],
+    'path.to.value2': [{
+      event: DataEvent.onValueInitOrChange,
+      effect: ((params) => {
+        const { context, value } = params;
+
+        context.node.scope.setVar(
+          'namespace_2',
+          ASTFactory.createVariableDeclaration({
+            meta: {
+              title: `Your Output Variable Title 2`,
+            },
+            key: `uid_${node.id}_2`,
+            type: createTypeFromValue(value),
+          })
+        )
+
+        console.log("View generated variables", context.node.scope.getVar('namespace_2'))
+
+      }) as Effect,
+    }],
+  },
+  render: () => (
+    // ...
+  )
+}
+```
+
+
+### Method 2: Sync Variables via Plugins
+
+In addition to static configuration in the form, we can also freely and dynamically operate the variables of the node in the plugin (Plugin) through `node.scope`.
+
+
+#### Update the Scope of the specified node
+
+The following example demonstrates how to get the `Scope` of the start node in the `onInit` life cycle of the plugin and perform a series of operations on its variables.
 
 ```tsx pure title="sync-variable-plugin.tsx"
 import {
@@ -188,43 +240,220 @@ import {
   PluginCreator,
 } from '@flowgram.ai/fixed-layout-editor';
 
-
 export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
   definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
     onInit(ctx, options) {
       const startNode = ctx.get(FlowDocument).getNode('start_0');
       const startScope =  startNode.scope!
 
-      // 1. Set Variable For Start Scope
+      // Set Variable For Start Scope
       startScope.setVar(
         ASTFactory.createVariableDeclaration({
           meta: {
             title: `Your Output Variable Title`,
           },
-          key: `your_variable_unique_key`,
+          key: `uid`,
           type: ASTFactory.createString(),
         })
       )
+    }
+  })
+```
 
-      // 2. Create, Update, Read, Delete Variable in namespace_2
-      startScope.setVar(
-        'namespace_2',
-        ASTFactory.createVariableDeclaration({
-          meta: {
-            title: `Your Output Variable Title 2`,
-          },
-          key: `your_variable_global_unique_key_2`,
-          type: ASTFactory.createString(),
-        })
-      )
+#### Sync variables in onNodeCreate
+
+The following example demonstrates how to get the Scope of a newly created node through `onNodeCreate` and synchronize variables by listening to `node.form.onFormValuesChange`.
 
-      console.log(startScope.getVar('namespace_2'))
+```tsx pure title="sync-variable-plugin.tsx"
+import {
+  FlowDocument,
+  definePluginCreator,
+  PluginCreator,
+} from '@flowgram.ai/fixed-layout-editor';
 
-      // 3. Delete Variable in namespace_2
-      startScope.clearVar('namespace_2')
+export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
+  definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
+    onInit(ctx, options) {
+      ctx.get(FlowDocument).onNodeCreate(({ node }) => {
+        const syncVariable = (title: string) => {
+          node.scope?.setVar(
+            ASTFactory.createVariableDeclaration({
+              key: `uid_${node.id}`,
+              meta: {
+                title,
+                icon: iconVariable,
+              },
+              type: ASTFactory.createString(),
+            })
+          );
+        };
+
+        if (node.form) {
+          // sync variable on init
+          syncVariable(node.form.getValueIn('title'));
+
+          // listen to form values change
+          node.form?.onFormValuesChange(({ values, name }) => {
+            // title field changed
+            if (name.match(/^title/)) {
+              syncVariable(values[name]);
+            }
+          });
+        }
+      });
     }
   })
+```
+
+### Method 3: Sync Variables in UI (Not Recommended)
+
+:::warning
+Directly synchronizing variables in the UI (Method 3) is a highly discouraged practice.
+
+It breaks the principle of **separation of data and rendering**, leading to tight coupling between data and rendering:
+
+- **Unable to sync variables without UI**: Variables cannot be updated independently without the UI, leading to inconsistencies between data and rendering.
+- **Increased code complexity**: Directly manipulating variables in the UI increases the complexity of the UI logic, making the code harder to maintain.
+- **Performance issues**: Variable synchronization operations may trigger unnecessary re-rendering of UI components.
+
+:::
+
+The following example demonstrates how to synchronize and update variables through the `useCurrentScope` event in `formMeta.render`.
+
+```tsx pure title="form-meta.ts"
+import {
+  createEffectFromVariableProvider,
+  ASTFactory,
+} from '@flowgram.ai/fixed-layout-editor';
+
+/**
+ * Get information of multiple fields from the form
+ */
+const FormRender = () => {
+  /**
+   * Get the current scope for subsequent variable setting
+   */
+  const scope = useCurrentScope()
+
+  return <>
+    <UserCustomForm
+      onValuesChange={(values) => {
+        scope.setVar(
+          ASTFactory.createVariableDeclaration({
+            meta: {
+              title: values.title,
+            },
+            key: `uid`,
+            type: ASTFactory.createString(),
+          })
+        )
+      }}
+    />
+  </>
+}
+
+export const formMeta = {
+  render: () => <FormRender />
+}
+```
+
+## Outputting Node Private Variables
+
+Private variables are variables that can only be accessed within the current node and its child nodes.
 
+Private variables can be set and obtained through the private scope `node.privateScope`. Its scope chain relationship is shown in the following figure:
+
+```mermaid
+graph BT
+  subgraph Current_Node
+    Current_Node.scope -.depends on.-> Current_Node.privateScope
+    Child_Node_1.scope -.depends on.-> Current_Node.privateScope
+    Child_Node_2.scope -.depends on.-> Current_Node.privateScope
+  end
+
+  Current_Node -.both depend on.-> Upstream_Node.scope
+  Downstream_Node.scope -.depends on.-> Current_Node.scope
+  Downstream_Node.scope -.depends on.-> Upstream_Node.scope
+
+
+  style Current_Node.privateScope fill:#f9f,stroke:#333,stroke-width:3px
+  style Current_Node.scope stroke:#333,stroke-width:3px
+```
+
+Only two of the methods are listed below, and other methods can be deduced from the [Output Node Variables](#outputting-node-variables) method.
+
+### Method 1: Via `createEffectFromVariableProvider`
+
+`createEffectFromVariableProvider` provides the parameter `scope` to specify the scope of the variable.
+- When `scope` is set to `private`, the scope of the variable is the private scope of the current node `node.privateScope`
+- When `scope` is set to `public`, the scope of the variable is the scope of the current node `node.scope`
+
+```tsx pure title="form-meta.ts"
+import {
+  createEffectFromVariableProvider,
+  ASTFactory,
+} from '@flowgram.ai/fixed-layout-editor';
+
+export const formMeta =  {
+  effect: {
+    // Create variable in privateScope
+    // = node.privateScope.setVar('path.to.value', ASTFactory.createVariableDeclaration(parse(v)))
+    'path.to.value': createEffectFromVariableProvider({
+      scope: 'private',
+      // parse form value to variable
+      parse(v: string) {
+        return {
+          meta: {
+            title: `Your Private Variable Title`,
+          },
+          key: `uid_${node.id}_locals`,
+          type: createTypeFromValue(v)
+        }
+      }
+    }),
+  },
+  render: () => (
+    // ...
+  )
+}
+```
+
+
+### Method 2: Via `node.privateScope`
+
+
+The API of `node.privateScope` is designed to be almost identical to the node scope (`node.scope`), providing methods such as `setVar`, `getVar`, `clearVar`, and also supporting namespaces. For details, please refer to [`node.scope`](#using-the-nodescope-api-in-side-effects).
+
+
+```tsx pure title="form-meta.tsx"
+import { Effect } from '@flowgram.ai/editor';
+
+export const formMeta = {
+  effect: {
+    'path.to.value': [{
+      event: DataEvent.onValueInitOrChange,
+      effect: ((params) => {
+        const { context, value } = params;
+
+        context.node.privateScope.setVar(
+          ASTFactory.createVariableDeclaration({
+            meta: {
+              title: `Your Private Variable Title`,
+            },
+            key: `uid_${node.id}`,
+            type: createTypeFromValue(value),
+          })
+        )
+
+        console.log("View generated variables", context.node.privateScope.getVar())
+
+      }) as Effect,
+    }],
+  },
+  render: () => (
+    // ...
+  )
+}
 ```
 
 
@@ -269,7 +498,7 @@ export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions
 ```
 
 
-### Method 2: Obtain in React Component
+### Method 2: Obtain in UI
 
 If you want to interact with global variables in the React component of the canvas, you can use the `useService` Hook to get the instance of `GlobalScope`:
 
@@ -291,7 +520,7 @@ function GlobalVariableComponent() {
         meta: {
           title: `Your Output Variable Title`,
         },
-        key: `your_variable_global_unique_key_${v}`,
+        key: `uid_${v}`,
         type: ASTFactory.createString(),
       })
     )
@@ -306,7 +535,7 @@ function GlobalVariableComponent() {
 
 ### API of Global Scope
 
-The API of `GlobalScope` is designed to be almost identical to the node scope (`NodeScope`), providing methods such as `setVar`, `getVar`, `clearVar`, and also supporting namespaces. This consistent design greatly reduces our learning costs.
+The API of `GlobalScope` is designed to be almost identical to the node scope (`node.scope`), providing methods such as `setVar`, `getVar`, `clearVar`, and also supporting namespaces. For details, please refer to [`node.scope`](#using-the-nodescope-api-in-side-effects).
 
 Here is a comprehensive example of operating global variables in a plugin:
 
@@ -343,7 +572,7 @@ onInit(ctx, options) {
         meta: {
           title: `Your Output Variable Title 2`,
         },
-        key: `your_variable_global_unique_key_2`,
+        key: `uid_2`,
         type: ASTFactory.createString(),
       })
   )

+ 2 - 2
apps/docs/src/zh/_nav.json

@@ -2,12 +2,12 @@
   {
     "text": "指引",
     "link": "/guide/introduction",
-    "activeMatch": "/guide/introduction"
+    "activeMatch": "/guide/"
   },
   {
     "text": "物料",
     "link": "/materials/introduction",
-    "activeMatch": "/materials/introduction"
+    "activeMatch": "/materials/"
   },
   {
     "text": "例子",

+ 16 - 35
apps/docs/src/zh/guide/variable/variable-consume.mdx

@@ -1,14 +1,19 @@
 # 消费变量
 
-在 Flowgram 中,变量的管理和消费是构建动态、可交互应用的核心。理解如何有效地消费变量,对于开发者来说至关重要。这篇文档将带你深入了解在不同场景下消费变量的各种方式。
+在 Flowgram 中,变量的管理和消费是构建动态、可交互应用的核心。
+
+## 官方物料:`VariableSelector`
+
+为了让你能更轻松地在应用中集成变量选择的功能,官方物料提供了 `VariableSelector` 组件。详情可以阅读文档: [VariableSelector](/materials/components/variable-selector)
+
 
 ## 在节点内获取变量树
 
-在画布的节点中,我们常常需要获取当前作用域下可用的变量,并将它们以树形结构展示出来,方便用户进行选择和操作。`useAvailableVariables` 这个 React Hook 就是为此而生。
+在画布的节点中,我们常常需要获取当前作用域下可用的变量,并将它们以树形结构展示出来,方便用户进行选择和操作。
 
 ### `useAvailableVariables`
 
-`useAvailableVariables` 是一个轻量级的 Hook,它直接返回当前作用域可用的变量数组 (`VariableDeclaration[]`)。如果你只需要一个简单的变量列表,并且不需要进行更复杂的操作,那么 `useAvailableVariables` 会是更便捷的选择。
+`useAvailableVariables` 是一个轻量级的 Hook,它直接返回当前作用域可用的变量数组 (`VariableDeclaration[]`)。
 
 ```tsx pure title="use-variable-tree.tsx"
 import {
@@ -88,39 +93,14 @@ const renderVariable = (variable: BaseVariableField) => ({
 
 ```
 
-## 官方物料:`VariableSelector`
-
-为了让你能更轻松地在应用中集成变量选择的功能,我们为你准备了官方物料 —— `VariableSelector` 组件。它封装了前面提到的所有逻辑,让你无需从头开始,即可拥有一个功能强大、界面美观的变量选择器。
-
-<img loading="lazy" src="/materials/variable-selector.png" style={{width:500}}/>
-
-`VariableSelector` 不仅支持展示变量树,还内置了搜索、过滤等高级功能,可以极大地提升用户体验。更详细的介绍,请参考 [官方表单物料](/guide/form/form-materials.html) 文档。
-
-你可以通过以下两种方式来使用它:
-
-**1. 通过 NPM 包引用**
-
-这是最简单直接的方式,只需一行代码,即可在你的项目中引入 `VariableSelector`。
-
-```tsx
-import { VariableSelector } from '@flowgram.ai/form-materials';
-```
-
-**2. 通过 CLI 复制源代码**
 
-如果你希望对 `VariableSelector` 进行更深度的定制,我们也提供了通过 CLI 将组件源代码直接复制到你的项目中的方式。这样,你就可以随心所欲地修改它,以满足你独特的业务需求。
+## `ScopeAvailableData`
 
-```bash
-npx @flowgram.ai/materials components/variable-selector
-```
-
-## `ScopeAvailableData`:你的变量百宝箱
-
-`ScopeAvailableData` 对象是变量系统的核心之一,它由 `useScopeAvailable` Hook 返回,是你与作用域内可用变量进行交互的主要桥梁。你可以把它想象成一个功能强大的“变量工具箱”。
+`ScopeAvailableData` 对象是变量系统的核心之一,它由 `useScopeAvailable` Hook 返回,是与**作用域内可用变量**进行交互的主要桥梁。
 
 ### `useScopeAvailable`
 
-`useScopeAvailable` 是一个功能更强大的 Hook,它能够返回一个 `ScopeAvailableData` 对象,其中不仅包含了当前作用域所有可用的变量信息,还提供了一些高级 API,比如 `trackByKeyPath`。
+`useScopeAvailable` 能够返回一个 `ScopeAvailableData` 对象,其中不仅包含了当前作用域所有可用的变量信息,还提供了一些高级 API,比如 `trackByKeyPath`。
 
 它与 `useAvailableVariables` 的主要区别在于:
 
@@ -136,7 +116,6 @@ const available = useScopeAvailable();
 console.log(available.variables);
 ```
 
-
 ### 获取变量列表
 
 最基础的用法就是获取当前作用域下所有可用的变量。
@@ -160,7 +139,7 @@ function MyComponent() {
 }
 ```
 
-### 追踪单个变量的变化:`trackByKeyPath`
+### `trackByKeyPath`
 
 当你只关心某个特定变量(尤其是嵌套在 Object 或 Array 中的变量)的变化时,`trackByKeyPath` 就派上用场了。它能让你精准地“订阅”这个变量的更新,而不会因为其他不相关变量的变化导致组件重新渲染,从而实现更精细的性能优化。
 
@@ -193,9 +172,11 @@ function UserNameDisplay() {
 }
 ```
 
-### 高级事件监听 API
+### 整体监听 API
+
+除了 `trackByKeyPath`,`ScopeAvailableData` 还提供了一套整体变量变化的事件监听 API,让你能够更精细地控制变量变化的响应逻辑。
 
-除了 `trackByKeyPath`,`ScopeAvailableData` 还提供了一套更底层的事件监听 API,让你能够更精细地控制变量变化的响应逻辑。这在处理一些复杂的、需要手动管理订阅的场景时非常有用。
+这在处理一些复杂的、需要手动管理订阅的场景时非常有用。
 
 下面我们通过一个表格来详细对比这三个核心的监听 API:
 

+ 336 - 111
apps/docs/src/zh/guide/variable/variable-output.mdx

@@ -1,31 +1,26 @@
 # 输出变量
 
-在 Flowgram 中,变量是连接各个节点的关键枢纽。一个节点的输出,往往是另一个节点的输入。
+我们主要将输出变量分为三类:
 
-我们主要将变量分为两类:
-
-- **节点变量**:作用域仅限于单个节点,通常作为该节点的产出,供后续节点使用。
-- **全局变量**:作用域贯穿整个流程,任何节点都可以读取和修改,适合存放一些公共状态或配置。
+1. **输出节点变量**:通常作为该节点的产出,供后续节点使用。
+2. **输出节点私有变量**:输出变量仅限于节点内部(包括子节点),不能被外部节点访问。
+3. **输出全局变量**:贯穿整个流程,任何节点都可以读取,适合存放一些公共状态或配置。
 
 ## 输出节点变量
 
-输出节点变量,意味着这个变量和当前节点的生命周期是绑定的。
-
-当节点被创建时,变量就诞生了;当节点被删除时,变量也随之消逝。我们通常有两种方式来输出节点变量:
-
-1.  **通过表单配置**:在节点的设置面板(Form)中,通过一些预设的配置或自定义的逻辑来声明输出变量。这种方式非常直观,所见即所得。
-2.  **通过 API 调用**:在插件(Plugin)中,通过调用 `node.scope` API 来动态地为节点添加、修改或删除变量。这种方式更加灵活,适合处理复杂的、动态的业务场景。
+输出节点变量,意味着这个变量和当前节点的生命周期是绑定的。当节点被创建时,变量就诞生了;当节点被删除时,变量也随之消失。
 
-接下来,我们将深入探讨这两种方式的具体用法。
+我们通常有三种方式来输出节点变量:
 
-### 方式一:通过表单配置输出
+### 方式一:通过表单副作用同步
 
-在节点的 `form-meta.ts` 文件中进行配置,是定义节点输出变量最常见的方式。它又可以分为两种玩法:一种是“懒人版”,使用我们内置的物料;另一种是“大神版”,完全自定义。
+[表单副作用](/guide/form/form#副作用-effect) 通常在节点的 `form-meta.ts` 文件中进行配置,是定义节点输出变量最常见的方式。
 
 #### `provideJsonSchemaOutputs` 物料
 
-如果你的节点需要输出的变量,其结构恰好和表单的 `JSON Schema` 结构一致,那么恭喜你,`provideJsonSchemaOutputs` 这个副作用(Effect)物料就是为你量身定做的!
-它就像一个自动化的“变量搬运工”,能自动将表单中的 json schema,原封不动地转换为节点的输出变量。
+若节点所需输出变量的结构与 [JSON Schema](https://json-schema.org/) 结构匹配,即可使用 `provideJsonSchemaOutputs` 这一副作用(Effect)物料。
+
+它能够自动将表单的 JSON Schema 转换为节点的输出变量,无需额外配置。
 
 使用起来非常简单,只需要在 `formMeta` 的 `effect` 中加上两行配置即可:
 
@@ -43,20 +38,23 @@ export const formMeta = {
 };
 ```
 
-#### 通过表单副作用自定义输出
+#### 通过 `createEffectFromVariableProvider` 自定义输出
+
+`provideJsonSchemaOutputs` 虽然方便,但它只适配 `JsonSchema`。如果你想要定义自己的一套 Schema,那么就需要自定义表单的副作用。
 
-`provideJsonSchemaOutputs` 虽然方便,但它只适配 JsonSchema。
+Flowgram 提供了一个强大的辅助函数 `createEffectFromVariableProvider`
 
-如果你想要定义自己的一套 Schema,那么你就需要自定义表单的副作用了。
+只需要定义一个 `parse`函数,就可以自定义自己的变量同步逻辑:
+- `parse`函数会在表单值初始化和更新时被调用
+- `parse`函数的输入为当前字段的表单的值
+- `parse`函数的输出为变量 AST 信息
 
-Flowgram 提供了一个强大的辅助函数 `createEffectFromVariableProvider`,它能帮你轻松地创建一个用于提供变量的副作用。
-你可以把它想象成一个“变量加工厂”,输入的是表单数据,输出的是你精心加工后的变量。
+<br />
 
-下面这个例子中,我们为表单的两个字段 `path.to.value` 和 `path.to.value2` 分别创建了输出变量。当用户在表单中填写这两个字段时,`parse` 函数就会被触发,将用户输入的值(`v`)转换成一个标准的变量声明对象。
+下面这个例子中,我们为表单的两个字段 `path.to.value` 和 `path.to.value2` 分别创建了输出变量
 
-```tsx pure title="node-registries.ts"
+```tsx pure title="form-meta.ts"
 import {
-  FlowNodeRegistry,
   createEffectFromVariableProvider,
   ASTFactory,
   type ASTNodeJSON
@@ -78,47 +76,41 @@ export function createTypeFromValue(value: string): ASTNodeJSON | undefined {
   }
 }
 
-export const nodeRegistries: FlowNodeRegistry[] = [
-  {
-    type: 'start',
-    formMeta: {
-      effect: {
-        // Create first variable
-        // = variableData.setVar('path.to.value', ASTFactory.createVariableDeclaration(parse(v)))
-        'path.to.value': createEffectFromVariableProvider({
-          // parse form value to variable
-          parse(v: string) {
-            return {
-              meta: {
-                title: `Your Output Variable Title`,
-              },
-              key: `your_variable_global_unique_key_${node.id}`,
-              type: createTypeFromValue(v)
-            }
-          }
-        }),
-        // Create second variable
-        // = variableData.setVar('path.to.value2', ASTFactory.createVariableDeclaration(parse(v)))
-        'path.to.value2': createEffectFromVariableProvider({
-          // parse form value to variable
-          parse(v: string) {
-            return {
-              meta: {
-                title: `Your Output Variable Title 2`,
-              },
-              key: `your_variable_global_unique_key_${node.id}_2`,
-              type: createTypeFromValue(v)
-            }
-          }
-        }),
-      },
-      render: () => (
-       // ...
-      )
-    },
-  }
-]
-
+export const formMeta =  {
+  effect: {
+    // Create first variable
+    // = node.scope.setVar('path.to.value', ASTFactory.createVariableDeclaration(parse(v)))
+    'path.to.value': createEffectFromVariableProvider({
+      // parse form value to variable
+      parse(v: string) {
+        return {
+          meta: {
+            title: `Your Output Variable Title`,
+          },
+          key: `uid_${node.id}`,
+          type: createTypeFromValue(v)
+        }
+      }
+    }),
+    // Create second variable
+    // = node.scope.setVar('path.to.value2', ASTFactory.createVariableDeclaration(parse(v)))
+    'path.to.value2': createEffectFromVariableProvider({
+      // parse form value to variable
+      parse(v: string) {
+        return {
+          meta: {
+            title: `Your Output Variable Title 2`,
+          },
+          key: `uid_${node.id}_2`,
+          type: createTypeFromValue(v)
+        }
+      }
+    }),
+  },
+  render: () => (
+    // ...
+  )
+}
 ```
 
 #### 多个表单字段同步到一个变量上
@@ -127,12 +119,15 @@ export const nodeRegistries: FlowNodeRegistry[] = [
 
 
 ```tsx pure title="form-meta.ts"
+import {
+  createEffectFromVariableProvider,
+  ASTFactory,
+} from '@flowgram.ai/fixed-layout-editor';
 
 /**
- * 从 formValues 拿到多个字段的信息
+ * 从 form 拿到多个字段的信息
  */
 const variableSyncEffect = createEffectFromVariableProvider({
-
   // 必须添加,确保不同字段的副作用,同步到同一个命名空间上
   namespace: 'your_namespace',
 
@@ -142,7 +137,7 @@ const variableSyncEffect = createEffectFromVariableProvider({
       meta: {
         title: `Your Output Variable Title`,
       },
-      key: `your_variable_global_unique_key_${node.id}`,
+      key: `uid_${node.id}`,
       type: createTypeFromValue({
         value1: form.getValueIn('path.to.value'),
         value2: form.getValueIn('path.to.value2'),
@@ -151,31 +146,20 @@ const variableSyncEffect = createEffectFromVariableProvider({
   }
 })
 
-export const nodeRegistries: FlowNodeRegistry[] = [
-  {
-    type: 'start',
-    formMeta: {
-      effect: {
-        'path.to.value': variableSyncEffect,
-        'path.to.value2': variableSyncEffect,
-      },
-      render: () => (
-       // ...
-      )
-    },
-  }
-]
-
+export const formMeta = {
+  effect: {
+    'path.to.value': variableSyncEffect,
+    'path.to.value2': variableSyncEffect,
+  },
+  render: () => (
+   // ...
+  )
+}
 ```
 
+#### 在副作用中使用 `node.scope` API
 
-
-
-### 方式二:使用 `node.scope` API
-
-除了在表单中静态配置,我们还可以在插件(Plugin)中,通过 `node.scope` API 来获取作用域,从而动态地操作节点的变量。
-
-这种方式赋予了你极高的自由度,你可以在任何时机、任何地点,对节点的变量进行增、删、改、查。
+如果 `createEffectFromVariableProvider` 不能满足你的需求,你也可以直接在表单副作用中使用 `node.scope` API,进行更加灵活多变的变量操作。
 
 `node.scope` 会返回一个节点的变量作用域(Scope)对象,这个对象上挂载了几个核心方法:
 
@@ -186,6 +170,66 @@ export const nodeRegistries: FlowNodeRegistry[] = [
 - `clearVar()`: 清空所有变量。
 - `clearVar(namespace)`: 清空指定命名空间下的变量。
 
+
+```tsx pure title="form-meta.tsx"
+import { Effect } from '@flowgram.ai/editor';
+
+export const formMeta = {
+  effect: {
+    'path.to.value': [{
+      event: DataEvent.onValueInitOrChange,
+      effect: ((params) => {
+        const { context, value } = params;
+
+        context.node.scope.setVar(
+          ASTFactory.createVariableDeclaration({
+            meta: {
+              title: `Your Output Variable Title`,
+            },
+            key: `uid_${node.id}`,
+            type: createTypeFromValue(value),
+          })
+        )
+
+        console.log("查看生成的变量", context.node.scope.getVar())
+
+      }) as Effect,
+    }],
+    'path.to.value2': [{
+      event: DataEvent.onValueInitOrChange,
+      effect: ((params) => {
+        const { context, value } = params;
+
+        context.node.scope.setVar(
+          'namespace_2',
+          ASTFactory.createVariableDeclaration({
+            meta: {
+              title: `Your Output Variable Title 2`,
+            },
+            key: `uid_${node.id}_2`,
+            type: createTypeFromValue(value),
+          })
+        )
+
+        console.log("查看生成的变量", context.node.scope.getVar('namespace_2'))
+
+      }) as Effect,
+    }],
+  },
+  render: () => (
+    // ...
+  )
+}
+```
+
+
+### 方式二:通过插件同步变量
+
+除了在表单中静态配置,我们还可以在插件(Plugin)中,通过 `node.scope` 更新自由动态地操作节点的变量。
+
+
+#### 指定节点的 Scope 进行更新
+
 下面的例子演示了如何在插件的 `onInit`生命周期中,获取开始节点的 `Scope`,并对它的变量进行一系列操作。
 
 ```tsx pure title="sync-variable-plugin.tsx"
@@ -195,43 +239,224 @@ import {
   PluginCreator,
 } from '@flowgram.ai/fixed-layout-editor';
 
-
 export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
   definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
     onInit(ctx, options) {
       const startNode = ctx.get(FlowDocument).getNode('start_0');
       const startScope =  startNode.scope!
 
-      // 1. Set Variable For Start Scope
+      // Set Variable For Start Scope
       startScope.setVar(
         ASTFactory.createVariableDeclaration({
           meta: {
             title: `Your Output Variable Title`,
           },
-          key: `your_variable_unique_key`,
+          key: `uid`,
           type: ASTFactory.createString(),
         })
       )
+    }
+  })
+```
 
-      // 2. Create, Update, Read, Delete Variable in namespace_2
-      startScope.setVar(
-        'namespace_2',
-        ASTFactory.createVariableDeclaration({
-          meta: {
-            title: `Your Output Variable Title 2`,
-          },
-          key: `your_variable_global_unique_key_2`,
-          type: ASTFactory.createString(),
-        })
-      )
+#### 在 onNodeCreate 同步变量
 
-      console.log(startScope.getVar('namespace_2'))
+下面的例子演示了如何通过 `onNodeCreate` 获取到新创建节点的 Scope,并通过监听 `node.form.onFormValuesChange` 实现变量的同步操作。
+
+```tsx pure title="sync-variable-plugin.tsx"
+import {
+  FlowDocument,
+  definePluginCreator,
+  PluginCreator,
+} from '@flowgram.ai/fixed-layout-editor';
 
-      // 3. Delete Variable in namespace_2
-      startScope.clearVar('namespace_2')
+export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
+  definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
+    onInit(ctx, options) {
+      ctx.get(FlowDocument).onNodeCreate(({ node }) => {
+        const syncVariable = (title: string) => {
+          node.scope?.setVar(
+            ASTFactory.createVariableDeclaration({
+              key: `uid_${node.id}`,
+              meta: {
+                title,
+                icon: iconVariable,
+              },
+              type: ASTFactory.createString(),
+            })
+          );
+        };
+
+        if (node.form) {
+          // sync variable on init
+          syncVariable(node.form.getValueIn('title'));
+
+          // listen to form values change
+          node.form?.onFormValuesChange(({ values, name }) => {
+            // title field changed
+            if (name.match(/^title/)) {
+              syncVariable(values[name]);
+            }
+          });
+        }
+      });
     }
   })
+```
+
+### 方式三:在 UI 中同步变量(不推荐)
+
+:::warning
+直接在 UI 中同步变量(方式三)是一种非常不推荐的做法。
+
+它会打破**数据和渲染分离**的原则,会导致数据和渲染之间的紧密耦合:
+
+- **脱离 UI 无法同步变量**:脱离了 UI 就无法独立更新变量,会导致数据和渲染之间的不一致。
+- **增加代码复杂度**:直接在 UI 中操作变量,会增加 UI 逻辑的复杂性,使代码更难维护。
+- **性能问题**:变量的同步操作可能会触发 UI 组件的不必要的重渲染。
+:::
+
+下面的例子演示了如何在 `formMeta.render` 中,通过 `useCurrentScope` 事件,同步更新变量。
+
+```tsx pure title="form-meta.ts"
+import {
+  createEffectFromVariableProvider,
+  ASTFactory,
+} from '@flowgram.ai/fixed-layout-editor';
+
+/**
+ * 从 form 拿到多个字段的信息
+ */
+const FormRender = () => {
+  /**
+   * 获取到当前作用域,用于后续设置变量
+   */
+  const scope = useCurrentScope()
+
+  return <>
+    <UserCustomForm
+      onValuesChange={(values) => {
+        scope.setVar(
+          ASTFactory.createVariableDeclaration({
+            meta: {
+              title: values.title,
+            },
+            key: `uid`,
+            type: ASTFactory.createString(),
+          })
+        )
+      }}
+    />
+  </>
+}
+
+export const formMeta = {
+  render: () => <FormRender />
+}
+```
+
+
+
+
+
 
+## 输出节点私有变量
+
+私有变量是指只能在当前节点及其子节点中访问的变量。
+
+私有变量可以通过私有作用域 `node.privateScope` 来设置和获取,它的作用域链关系如下图所示:
+
+```mermaid
+graph BT
+  subgraph 当前节点
+    当前节点.scope -.依赖变量.-> 当前节点.privateScope
+    子节点_1.scope -.依赖变量.-> 当前节点.privateScope
+    子节点_2.scope -.依赖变量.-> 当前节点.privateScope
+  end
+
+  当前节点 -.都依赖变量.-> 上游节点.scope
+  下游节点.scope -.依赖变量.-> 当前节点.scope
+  下游节点.scope -.依赖变量.-> 上游节点.scope
+
+
+  style 当前节点.privateScope fill:#f9f,stroke:#333,stroke-width:3px
+  style 当前节点.scope stroke:#333,stroke-width:3px
+```
+
+下面只列举其中两种方式,其他方式可以根据[输出节点变量](#输出节点变量)的方式类推
+
+### 方式一:通过 `createEffectFromVariableProvider`
+
+`createEffectFromVariableProvider` 提供了参数 `scope`,用于指定变量的作用域。
+- `scope` 设置为 `private` 时,变量的作用域为当前节点的私有作用域 `node.privateScope`
+- `scope` 设置为 `public` 时,变量的作用域为当前节点的作用域 `node.scope`
+
+```tsx pure title="form-meta.ts"
+import {
+  createEffectFromVariableProvider,
+  ASTFactory,
+} from '@flowgram.ai/fixed-layout-editor';
+
+export const formMeta =  {
+  effect: {
+    // Create variable in privateScope
+    // = node.privateScope.setVar('path.to.value', ASTFactory.createVariableDeclaration(parse(v)))
+    'path.to.value': createEffectFromVariableProvider({
+      scope: 'private',
+      // parse form value to variable
+      parse(v: string) {
+        return {
+          meta: {
+            title: `Your Private Variable Title`,
+          },
+          key: `uid_${node.id}_locals`,
+          type: createTypeFromValue(v)
+        }
+      }
+    }),
+  },
+  render: () => (
+    // ...
+  )
+}
+```
+
+
+### 方式二:通过 `node.privateScope`
+
+
+`node.privateScope` 的 API 设计得和节点作用域(`node.scope`)几乎一模一样,都提供了 `setVar`、`getVar`、`clearVar`等方法,并且同样支持命名空间(namespace)。详情可以参考 [`node.scope`](#在副作用中使用-nodescope-api)。
+
+
+```tsx pure title="form-meta.tsx"
+import { Effect } from '@flowgram.ai/editor';
+
+export const formMeta = {
+  effect: {
+    'path.to.value': [{
+      event: DataEvent.onValueInitOrChange,
+      effect: ((params) => {
+        const { context, value } = params;
+
+        context.node.privateScope.setVar(
+          ASTFactory.createVariableDeclaration({
+            meta: {
+              title: `Your Private Variable Title`,
+            },
+            key: `uid_${node.id}`,
+            type: createTypeFromValue(value),
+          })
+        )
+
+        console.log("查看生成的变量", context.node.privateScope.getVar())
+
+      }) as Effect,
+    }],
+  },
+  render: () => (
+    // ...
+  )
+}
 ```
 
 
@@ -276,7 +501,7 @@ export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions
 ```
 
 
-### 方式二:在 React 组件中获取
+### 方式二:在 UI 中获取
 
 如果你想在画布的 React 组件中与全局变量交互,可以使用 `useService` 这个 Hook 来获取 `GlobalScope` 的实例:
 
@@ -298,7 +523,7 @@ function GlobalVariableComponent() {
         meta: {
           title: `Your Output Variable Title`,
         },
-        key: `your_variable_global_unique_key_${v}`,
+        key: `uid_${v}`,
         type: ASTFactory.createString(),
       })
     )
@@ -313,7 +538,7 @@ function GlobalVariableComponent() {
 
 ### 全局作用域的 API
 
-`GlobalScope` 的 API 设计得和节点作用域(`NodeScope`)几乎一模一样,都提供了 `setVar`、`getVar`、`clearVar` 等方法,并且同样支持命名空间(namespace)。这种一致性设计大大降低了我们的学习成本
+`GlobalScope` 的 API 设计得和节点作用域(`node.scope`)几乎一模一样,都提供了 `setVar`、`getVar`、`clearVar` 等方法,并且同样支持命名空间(namespace)。详情可以参考 [`node.scope`](#在副作用中使用-nodescope-api)
 
 下面是一个在插件中操作全局变量的综合示例:
 
@@ -350,7 +575,7 @@ onInit(ctx, options) {
         meta: {
           title: `Your Output Variable Title 2`,
         },
-        key: `your_variable_global_unique_key_2`,
+        key: `uid_2`,
         type: ASTFactory.createString(),
       })
   )

+ 3 - 3
packages/variable-engine/variable-core/src/ast/common/map-node.ts

@@ -45,7 +45,7 @@ export class MapNode extends ASTNode<MapNodeJSON> {
     return this.withBatchUpdate(updateChildNodeHelper).call(this, {
       getChildNode: () => this.get(key),
       removeChildNode: () => this.map.delete(key),
-      updateChildNode: nextNode => this.map.set(key, nextNode),
+      updateChildNode: (nextNode) => this.map.set(key, nextNode),
       nextJSON,
     }) as Node;
   }
@@ -65,7 +65,7 @@ export class MapNode extends ASTNode<MapNodeJSON> {
    * @param key
    * @returns
    */
-  get(key: string): ASTNode | undefined {
-    return this.map.get(key);
+  get<Node extends ASTNode = ASTNode>(key: string): Node | undefined {
+    return this.map.get(key) as Node | undefined;
   }
 }

+ 8 - 5
packages/variable-engine/variable-core/src/scope/scope.ts

@@ -129,7 +129,7 @@ export class Scope<ScopeMeta extends Record<string, any> = Record<string, any>>
    * @param json - The JSON value to store.
    * @returns The updated AST node.
    */
-  public setVar(json: ASTNodeJSON): ASTNode;
+  public setVar<Node extends ASTNode = ASTNode>(json: ASTNodeJSON): Node;
 
   /**
    * Sets a variable in the Scope by key.
@@ -138,9 +138,12 @@ export class Scope<ScopeMeta extends Record<string, any> = Record<string, any>>
    * @param json - The JSON value to store.
    * @returns The updated AST node.
    */
-  public setVar(key: string, json: ASTNodeJSON): ASTNode;
+  public setVar<Node extends ASTNode = ASTNode>(key: string, json: ASTNodeJSON): Node;
 
-  public setVar(arg1: string | ASTNodeJSON, arg2?: ASTNodeJSON): ASTNode {
+  public setVar<Node extends ASTNode = ASTNode>(
+    arg1: string | ASTNodeJSON,
+    arg2?: ASTNodeJSON
+  ): Node {
     if (typeof arg1 === 'string' && arg2 !== undefined) {
       return this.ast.set(arg1, arg2);
     }
@@ -158,8 +161,8 @@ export class Scope<ScopeMeta extends Record<string, any> = Record<string, any>>
    * @param key - The key of the variable to retrieve. Defaults to 'outputs'.
    * @returns The value of the variable, or undefined if not found.
    */
-  public getVar(key: string = 'outputs') {
-    return this.ast.get(key);
+  public getVar<Node extends ASTNode = ASTNode>(key: string = 'outputs') {
+    return this.ast.get<Node>(key);
   }
 
   /**