| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- # 输出变量
- 我们主要将输出变量分为三类:
- 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<SyncVariablePluginOptions> =
- definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
- 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<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 当前节点
- 子节点_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<SyncVariablePluginOptions> =
- definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
- 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 <Input onChange={handleChange}/>
- }
- ```
- ### 全局作用域的 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)
|