# 输出变量 我们主要将输出变量分为三类: 1. **输出节点变量**:通常作为该节点的产出,供后续节点使用。 2. **输出节点私有变量**:输出变量仅限于节点内部(包括子节点),不能被外部节点访问。 3. **输出全局变量**:贯穿整个流程,任何节点都可以读取,适合存放一些公共状态或配置。 ## 输出节点变量 输出节点变量,意味着这个变量和当前节点的生命周期是绑定的。当节点被创建时,变量就诞生了;当节点被删除时,变量也随之消失。 我们通常有三种方式来输出节点变量: ### 方式一:通过表单副作用同步 [表单副作用](/guide/form/form#副作用-effect) 通常在节点的 `form-meta.ts` 文件中进行配置,是定义节点输出变量最常见的方式。 #### `provideJsonSchemaOutputs` 物料 若节点所需输出变量的结构与 [JSON Schema](https://json-schema.org/) 结构匹配,即可使用 `provideJsonSchemaOutputs` 这一副作用(Effect)物料。 只需要在 `formMeta` 的 `effect` 中加上两行配置即可: ```tsx pure title="form-meta.ts" import { syncVariableTitle, provideJsonSchemaOutputs, } from '@flowgram.ai/form-materials'; export const formMeta = { effect: { title: syncVariableTitle, // 变量标题自动同步 outputs: provideJsonSchemaOutputs, }, }; ``` #### 通过 `createEffectFromVariableProvider` 自定义输出 `provideJsonSchemaOutputs` 只适配 `JsonSchema`。如果你想要定义自己的一套 Schema,那么就需要自定义表单的副作用。 :::note Flowgram 提供了 `createEffectFromVariableProvider`,只需要定义一个 `parse`函数,就可以自定义自己的变量同步副作用: - `parse` 会在表单值初始化和更新时被调用 - `parse` 的输入为当前字段的表单的值 - `parse` 的输出为变量 AST 信息 ::: 下面这个例子中,我们为表单的两个字段 `path.to.value` 和 `path.to.value2` 分别创建了输出变量: ```tsx pure title="form-meta.ts" import { createEffectFromVariableProvider, ASTFactory, type ASTNodeJSON } from '@flowgram.ai/fixed-layout-editor'; export function createTypeFromValue(value: string): ASTNodeJSON | undefined { switch (value) { case 'string': return ASTFactory.createString(); case 'number': return ASTFactory.createNumber(); case 'boolean': return ASTFactory.createBoolean(); case 'integer': return ASTFactory.createInteger(); default: return; } } 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: () => ( // ... ) } ``` #### 多个表单字段同步到一个变量上 如果多个字段同步到一个变量,就需要用到 `createEffectFromVariableProvider` 的 `namespace` 字段,将多个字段的变量数据同步到同一个命名空间上。 ```tsx pure title="form-meta.ts" import { createEffectFromVariableProvider, ASTFactory, } from '@flowgram.ai/fixed-layout-editor'; /** * 从 form 拿到多个字段的信息 */ const variableSyncEffect = createEffectFromVariableProvider({ // 必须添加,确保不同字段的副作用,同步到同一个命名空间上 namespace: 'your_namespace', // 将表单值解析为变量 parse(_, { form, node }) { return { meta: { title: `Your Output Variable Title`, }, key: `uid_${node.id}`, type: createTypeFromValue({ value1: form.getValueIn('path.to.value'), value2: form.getValueIn('path.to.value2'), }) } } }) export const formMeta = { effect: { 'path.to.value': variableSyncEffect, 'path.to.value2': variableSyncEffect, }, render: () => ( // ... ) } ``` #### 在副作用中使用 `node.scope` API 如果 `createEffectFromVariableProvider` 不能满足你的需求,你也可以直接在表单副作用中使用 `node.scope` API,进行更加灵活多变的变量操作。 :::note `node.scope` 会返回一个节点的变量作用域(Scope)对象,这个对象上挂载了几个核心方法: - `setVar(variable)`: 设置一个变量。 - `setVar(namespace, variable)`: 在指定的命名空间下设置一个变量。 - `getVar()`: 获取所有变量。 - `getVar(namespace)`: 获取指定命名空间下的变量。 - `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" import { FlowDocument, definePluginCreator, PluginCreator, } from '@flowgram.ai/fixed-layout-editor'; export const createSyncVariablePlugin: PluginCreator = definePluginCreator({ onInit(ctx, options) { const startNode = ctx.get(FlowDocument).getNode('start_0'); const startScope = startNode.scope! // Set Variable For Start Scope startScope.setVar( ASTFactory.createVariableDeclaration({ meta: { title: `Your Output Variable Title`, }, key: `uid`, type: ASTFactory.createString(), }) ) } }) ``` #### 在 onNodeCreate 同步变量 下面的例子演示了如何通过 `onNodeCreate` 获取到新创建节点的 Scope,并通过监听 `node.form.onFormValuesChange` 实现变量的同步操作。 ```tsx pure title="sync-variable-plugin.tsx" import { FlowDocument, definePluginCreator, PluginCreator, } from '@flowgram.ai/fixed-layout-editor'; export const createSyncVariablePlugin: PluginCreator = definePluginCreator({ 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 <> { scope.setVar( ASTFactory.createVariableDeclaration({ meta: { title: values.title, }, key: `uid`, type: ASTFactory.createString(), }) ) }} /> } export const formMeta = { render: () => } ``` ## 输出节点私有变量 私有变量是指只能在当前节点及其子节点中访问的变量。 私有变量可以通过私有作用域 `node.privateScope` 来设置和获取,它的作用域链关系如下图所示: ```mermaid graph BT subgraph 当前节点 子节点_1.scope -.依赖变量.-> 当前节点.privateScope 当前节点.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: () => ( // ... ) } ``` ## 输出全局变量 全局变量就像是整个流程的“共享内存”,任何节点、任何插件都可以访问和修改它。它非常适合用来存储一些贯穿始终的状态,比如用户信息、环境配置等等。 和节点变量类似,我们也有两种主要的方式来获取全局变量的作用域(`GlobalScope`)。 ### 方式一:在插件中获取 在插件的上下文中(`ctx`),我们可以直接“注入”`GlobalScope` 的实例: ```tsx pure title="global-variable-plugin.tsx" import { GlobalScope, definePluginCreator, PluginCreator } from '@flowgram.ai/fixed-layout-editor'; export const createGlobalVariablePlugin: PluginCreator = definePluginCreator({ onInit(ctx, options) { const globalScope = ctx.get(GlobalScope) globalScope.setVar( ASTFactory.createVariableDeclaration({ meta: { title: `Your Output Variable Title`, }, key: `your_variable_global_unique_key`, type: ASTFactory.createString(), }) ) } }) ``` ### 方式二:在 UI 中获取 如果你想在画布的 React 组件中与全局变量交互,可以使用 `useService` 这个 Hook 来获取 `GlobalScope` 的实例: ```tsx pure title="global-variable-component.tsx" import { GlobalScope, useService, } from '@flowgram.ai/fixed-layout-editor'; function GlobalVariableComponent() { const globalScope = useService(GlobalScope) // ... const handleChange = (v: string) => { globalScope.setVar( ASTFactory.createVariableDeclaration({ meta: { title: `Your Output Variable Title`, }, key: `uid_${v}`, type: ASTFactory.createString(), }) ) } return } ``` ### 全局作用域的 API `GlobalScope` 的 API 设计得和节点作用域(`node.scope`)几乎一模一样,都提供了 `setVar`、`getVar`、`clearVar` 等方法,并且同样支持命名空间(namespace)。详情可以参考 [`node.scope`](#在副作用中使用-nodescope-api)。 下面是一个在插件中操作全局变量的综合示例: ```tsx pure title="sync-variable-plugin.tsx" import { GlobalScope, } from '@flowgram.ai/fixed-layout-editor'; // ... onInit(ctx, options) { const globalScope = ctx.get(GlobalScope); // 1. Create, Update, Read, Delete Variable in GlobalScope globalScope.setVar( ASTFactory.createVariableDeclaration({ meta: { title: `Your Output Variable Title`, }, key: `your_variable_global_unique_key`, type: ASTFactory.createString(), }) ) console.log(globalScope.getVar()) globalScope.clearVar() // 2. Create, Update, Read, Delete Variable in GlobalScope's namespace: 'namespace_1' globalScope.setVar( 'namespace_1', ASTFactory.createVariableDeclaration({ meta: { title: `Your Output Variable Title 2`, }, key: `uid_2`, type: ASTFactory.createString(), }) ) console.log(globalScope.getVar('namespace_1')) globalScope.clearVar('namespace_1') // ... } ``` 详见:[Class: GlobalScope](https://flowgram.ai/auto-docs/editor/classes/GlobalScope.html)