variable-output.mdx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. # Outputting Variables
  2. We mainly divide output variables into three categories:
  3. 1. **Output Node Variables**: Usually as the output of that node for subsequent nodes to use.
  4. 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.
  5. 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.
  6. ## Outputting Node Variables
  7. 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.
  8. We usually have three ways to output node variables:
  9. ### Method 1: Sync via Form Side Effects
  10. [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.
  11. #### `provideJsonSchemaOutputs` Material
  12. 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.
  13. It is very simple to use, just add two lines of configuration in the `effect` of `formMeta`:
  14. ```tsx pure title="form-meta.ts"
  15. import {
  16. syncVariableTitle,
  17. provideJsonSchemaOutputs,
  18. } from '@flowgram.ai/form-materials';
  19. export const formMeta = {
  20. effect: {
  21. title: syncVariableTitle, // Variable title is automatically synchronized
  22. outputs: provideJsonSchemaOutputs,
  23. },
  24. };
  25. ```
  26. #### Via `createEffectFromVariableProvider`
  27. `provideJsonSchemaOutputs` 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.
  28. :::note
  29. Flowgram provides `createEffectFromVariableProvider`, you only need to define a `parse` function to customize your own variable synchronization side effects:
  30. - `parse` will be called when the form value is initialized and updated
  31. - The input of `parse` is the value of the current field's form
  32. - The output of `parse` is the variable AST information
  33. :::
  34. In the following example, we create output variables for the two fields of the form `path.to.value` and `path.to.value2`:
  35. ```tsx pure title="form-meta.ts"
  36. import {
  37. createEffectFromVariableProvider,
  38. ASTFactory,
  39. type ASTNodeJSON
  40. } from '@flowgram.ai/fixed-layout-editor';
  41. export function createTypeFromValue(value: string): ASTNodeJSON | undefined {
  42. switch (value) {
  43. case 'string':
  44. return ASTFactory.createString();
  45. case 'number':
  46. return ASTFactory.createNumber();
  47. case 'boolean':
  48. return ASTFactory.createBoolean();
  49. case 'integer':
  50. return ASTFactory.createInteger();
  51. default:
  52. return;
  53. }
  54. }
  55. export const formMeta = {
  56. effect: {
  57. // Create first variable
  58. // = node.scope.setVar('path.to.value', ASTFactory.createVariableDeclaration(parse(v)))
  59. 'path.to.value': createEffectFromVariableProvider({
  60. // parse form value to variable
  61. parse(v: string) {
  62. return {
  63. meta: {
  64. title: `Your Output Variable Title`,
  65. },
  66. key: `uid_${node.id}`,
  67. type: createTypeFromValue(v)
  68. }
  69. }
  70. }),
  71. // Create second variable
  72. // = node.scope.setVar('path.to.value2', ASTFactory.createVariableDeclaration(parse(v)))
  73. 'path.to.value2': createEffectFromVariableProvider({
  74. // parse form value to variable
  75. parse(v: string) {
  76. return {
  77. meta: {
  78. title: `Your Output Variable Title 2`,
  79. },
  80. key: `uid_${node.id}_2`,
  81. type: createTypeFromValue(v)
  82. }
  83. }
  84. }),
  85. },
  86. render: () => (
  87. // ...
  88. )
  89. }
  90. ```
  91. #### Syncing multiple form fields to one variable
  92. 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.
  93. ```tsx pure title="form-meta.ts"
  94. import {
  95. createEffectFromVariableProvider,
  96. ASTFactory,
  97. } from '@flowgram.ai/fixed-layout-editor';
  98. /**
  99. * Get information of multiple fields from the form
  100. */
  101. const variableSyncEffect = createEffectFromVariableProvider({
  102. // Must be added to ensure that the side effects of different fields are synchronized to the same namespace
  103. namespace: 'your_namespace',
  104. // Parse the form value into a variable
  105. parse(_, { form, node }) {
  106. return {
  107. meta: {
  108. title: `Your Output Variable Title`,
  109. },
  110. key: `uid_${node.id}`,
  111. type: createTypeFromValue({
  112. value1: form.getValueIn('path.to.value'),
  113. value2: form.getValueIn('path.to.value2'),
  114. })
  115. }
  116. }
  117. })
  118. export const formMeta = {
  119. effect: {
  120. 'path.to.value': variableSyncEffect,
  121. 'path.to.value2': variableSyncEffect,
  122. },
  123. render: () => (
  124. // ...
  125. )
  126. }
  127. ```
  128. #### Using the `node.scope` API in Side Effects
  129. 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.
  130. :::note
  131. `node.scope` will return a node's variable scope (Scope) object, which has several core methods:
  132. - `setVar(variable)`: Set a variable.
  133. - `setVar(namespace, variable)`: Set a variable under the specified namespace.
  134. - `getVar()`: Get all variables.
  135. - `getVar(namespace)`: Get the variables under the specified namespace.
  136. - `clearVar()`: Clear all variables.
  137. - `clearVar(namespace)`: Clear the variables under the specified namespace.
  138. :::
  139. ```tsx pure title="form-meta.tsx"
  140. import { Effect } from '@flowgram.ai/editor';
  141. export const formMeta = {
  142. effect: {
  143. 'path.to.value': [{
  144. event: DataEvent.onValueInitOrChange,
  145. effect: ((params) => {
  146. const { context, value } = params;
  147. context.node.scope.setVar(
  148. ASTFactory.createVariableDeclaration({
  149. meta: {
  150. title: `Your Output Variable Title`,
  151. },
  152. key: `uid_${node.id}`,
  153. type: createTypeFromValue(value),
  154. })
  155. )
  156. console.log("View generated variables", context.node.scope.getVar())
  157. }) as Effect,
  158. }],
  159. 'path.to.value2': [{
  160. event: DataEvent.onValueInitOrChange,
  161. effect: ((params) => {
  162. const { context, value } = params;
  163. context.node.scope.setVar(
  164. 'namespace_2',
  165. ASTFactory.createVariableDeclaration({
  166. meta: {
  167. title: `Your Output Variable Title 2`,
  168. },
  169. key: `uid_${node.id}_2`,
  170. type: createTypeFromValue(value),
  171. })
  172. )
  173. console.log("View generated variables", context.node.scope.getVar('namespace_2'))
  174. }) as Effect,
  175. }],
  176. },
  177. render: () => (
  178. // ...
  179. )
  180. }
  181. ```
  182. ### Method 2: Sync Variables via Plugins
  183. 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`.
  184. #### Update the Scope of the specified node
  185. 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.
  186. ```tsx pure title="sync-variable-plugin.tsx"
  187. import {
  188. FlowDocument,
  189. definePluginCreator,
  190. PluginCreator,
  191. } from '@flowgram.ai/fixed-layout-editor';
  192. export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
  193. definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
  194. onInit(ctx, options) {
  195. const startNode = ctx.get(FlowDocument).getNode('start_0');
  196. const startScope = startNode.scope!
  197. // Set Variable For Start Scope
  198. startScope.setVar(
  199. ASTFactory.createVariableDeclaration({
  200. meta: {
  201. title: `Your Output Variable Title`,
  202. },
  203. key: `uid`,
  204. type: ASTFactory.createString(),
  205. })
  206. )
  207. }
  208. })
  209. ```
  210. #### Sync variables in onNodeCreate
  211. 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`.
  212. ```tsx pure title="sync-variable-plugin.tsx"
  213. import {
  214. FlowDocument,
  215. definePluginCreator,
  216. PluginCreator,
  217. } from '@flowgram.ai/fixed-layout-editor';
  218. export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
  219. definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
  220. onInit(ctx, options) {
  221. ctx.get(FlowDocument).onNodeCreate(({ node }) => {
  222. const syncVariable = (title: string) => {
  223. node.scope?.setVar(
  224. ASTFactory.createVariableDeclaration({
  225. key: `uid_${node.id}`,
  226. meta: {
  227. title,
  228. icon: iconVariable,
  229. },
  230. type: ASTFactory.createString(),
  231. })
  232. );
  233. };
  234. if (node.form) {
  235. // sync variable on init
  236. syncVariable(node.form.getValueIn('title'));
  237. // listen to form values change
  238. node.form?.onFormValuesChange(({ values, name }) => {
  239. // title field changed
  240. if (name.match(/^title/)) {
  241. syncVariable(values[name]);
  242. }
  243. });
  244. }
  245. });
  246. }
  247. })
  248. ```
  249. ### Method 3: Sync Variables in UI (Not Recommended)
  250. :::warning
  251. Directly synchronizing variables in the UI (Method 3) is a highly discouraged practice.
  252. It breaks the principle of **separation of data and rendering**, leading to tight coupling between data and rendering:
  253. - **Unable to sync variables without UI**: Variables cannot be updated independently without the UI, leading to inconsistencies between data and rendering.
  254. - **Increased code complexity**: Directly manipulating variables in the UI increases the complexity of the UI logic, making the code harder to maintain.
  255. - **Performance issues**: Variable synchronization operations may trigger unnecessary re-rendering of UI components.
  256. :::
  257. ```tsx pure title="form-meta.ts"
  258. import {
  259. createEffectFromVariableProvider,
  260. ASTFactory,
  261. } from '@flowgram.ai/fixed-layout-editor';
  262. /**
  263. * Get information of multiple fields from the form
  264. */
  265. const FormRender = () => {
  266. /**
  267. * Get the current scope for subsequent variable setting
  268. */
  269. const scope = useCurrentScope()
  270. return <>
  271. <UserCustomForm
  272. onValuesChange={(values) => {
  273. scope.setVar(
  274. ASTFactory.createVariableDeclaration({
  275. meta: {
  276. title: values.title,
  277. },
  278. key: `uid`,
  279. type: ASTFactory.createString(),
  280. })
  281. )
  282. }}
  283. />
  284. </>
  285. }
  286. export const formMeta = {
  287. render: () => <FormRender />
  288. }
  289. ```
  290. ## Outputting Node Private Variables
  291. Private variables are variables that can only be accessed within the current node and its child nodes.
  292. Private variables can be set and obtained through the private scope `node.privateScope`. Its scope chain relationship is shown in the following figure:
  293. ```mermaid
  294. graph BT
  295. subgraph Current_Node
  296. Child_Node_1.scope -.depends on.-> Current_Node.privateScope
  297. Current_Node.scope -.depends on.-> Current_Node.privateScope
  298. Child_Node_2.scope -.depends on.-> Current_Node.privateScope
  299. end
  300. Current_Node -.all depend on.-> Upstream_Node.scope
  301. Downstream_Node.scope -.depends on.-> Current_Node.scope
  302. Downstream_Node.scope -.depends on.-> Upstream_Node.scope
  303. style Current_Node.privateScope fill:#f9f,stroke:#333,stroke-width:3px
  304. style Current_Node.scope stroke:#333,stroke-width:3px
  305. ```
  306. Only two of the methods are listed below, and other methods can be deduced from the [Output Node Variables](#outputting-node-variables) method.
  307. ### Method 1: Via `createEffectFromVariableProvider`
  308. `createEffectFromVariableProvider` provides the parameter `scope` to specify the scope of the variable.
  309. - When `scope` is set to `private`, the scope of the variable is the private scope of the current node `node.privateScope`
  310. - When `scope` is set to `public`, the scope of the variable is the scope of the current node `node.scope`
  311. ```tsx pure title="form-meta.ts"
  312. import {
  313. createEffectFromVariableProvider,
  314. ASTFactory,
  315. } from '@flowgram.ai/fixed-layout-editor';
  316. export const formMeta = {
  317. effect: {
  318. // Create variable in privateScope
  319. // = node.privateScope.setVar('path.to.value', ASTFactory.createVariableDeclaration(parse(v)))
  320. 'path.to.value': createEffectFromVariableProvider({
  321. scope: 'private',
  322. // parse form value to variable
  323. parse(v: string) {
  324. return {
  325. meta: {
  326. title: `Your Private Variable Title`,
  327. },
  328. key: `uid_${node.id}_locals`,
  329. type: createTypeFromValue(v)
  330. }
  331. }
  332. }),
  333. },
  334. render: () => (
  335. // ...
  336. )
  337. }
  338. ```
  339. ### Method 2: Via `node.privateScope`
  340. 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).
  341. ```tsx pure title="form-meta.tsx"
  342. import { Effect } from '@flowgram.ai/editor';
  343. export const formMeta = {
  344. effect: {
  345. 'path.to.value': [{
  346. event: DataEvent.onValueInitOrChange,
  347. effect: ((params) => {
  348. const { context, value } = params;
  349. context.node.privateScope.setVar(
  350. ASTFactory.createVariableDeclaration({
  351. meta: {
  352. title: `Your Private Variable Title`,
  353. },
  354. key: `uid_${node.id}`,
  355. type: createTypeFromValue(value),
  356. })
  357. )
  358. console.log("View generated variables", context.node.privateScope.getVar())
  359. }) as Effect,
  360. }],
  361. },
  362. render: () => (
  363. // ...
  364. )
  365. }
  366. ```
  367. ## Outputting Global Variables
  368. Global variables are like the "shared memory" of the entire process, which can be accessed and modified by any node and any plugin. It is very suitable for storing some states that run through, such as user information, environment configuration, and so on.
  369. Similar to node variables, we also have two main ways to obtain the scope of global variables (`GlobalScope`).
  370. ### Method 1: Obtain in Plugin
  371. In the context of the plugin (`ctx`), we can directly "inject" the instance of `GlobalScope`:
  372. ```tsx pure title="global-variable-plugin.tsx"
  373. import {
  374. GlobalScope,
  375. definePluginCreator,
  376. PluginCreator
  377. } from '@flowgram.ai/fixed-layout-editor';
  378. export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
  379. definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
  380. onInit(ctx, options) {
  381. const globalScope = ctx.get(GlobalScope)
  382. globalScope.setVar(
  383. ASTFactory.createVariableDeclaration({
  384. meta: {
  385. title: `Your Output Variable Title`,
  386. },
  387. key: `your_variable_global_unique_key`,
  388. type: ASTFactory.createString(),
  389. })
  390. )
  391. }
  392. })
  393. ```
  394. ### Method 2: Obtain in UI
  395. 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`:
  396. ```tsx pure title="global-variable-component.tsx"
  397. import {
  398. GlobalScope,
  399. useService,
  400. } from '@flowgram.ai/fixed-layout-editor';
  401. function GlobalVariableComponent() {
  402. const globalScope = useService(GlobalScope)
  403. // ...
  404. const handleChange = (v: string) => {
  405. globalScope.setVar(
  406. ASTFactory.createVariableDeclaration({
  407. meta: {
  408. title: `Your Output Variable Title`,
  409. },
  410. key: `uid_${v}`,
  411. type: ASTFactory.createString(),
  412. })
  413. )
  414. }
  415. return <Input onChange={handleChange}/>
  416. }
  417. ```
  418. ### API of Global Scope
  419. 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).
  420. Here is a comprehensive example of operating global variables in a plugin:
  421. ```tsx pure title="sync-variable-plugin.tsx"
  422. import {
  423. GlobalScope,
  424. } from '@flowgram.ai/fixed-layout-editor';
  425. // ...
  426. onInit(ctx, options) {
  427. const globalScope = ctx.get(GlobalScope);
  428. // 1. Create, Update, Read, Delete Variable in GlobalScope
  429. globalScope.setVar(
  430. ASTFactory.createVariableDeclaration({
  431. meta: {
  432. title: `Your Output Variable Title`,
  433. },
  434. key: `your_variable_global_unique_key`,
  435. type: ASTFactory.createString(),
  436. })
  437. )
  438. console.log(globalScope.getVar())
  439. globalScope.clearVar()
  440. // 2. Create, Update, Read, Delete Variable in GlobalScope's namespace: 'namespace_1'
  441. globalScope.setVar(
  442. 'namespace_1',
  443. ASTFactory.createVariableDeclaration({
  444. meta: {
  445. title: `Your Output Variable Title 2`,
  446. },
  447. key: `uid_2`,
  448. type: ASTFactory.createString(),
  449. })
  450. )
  451. console.log(globalScope.getVar('namespace_1'))
  452. globalScope.clearVar('namespace_1')
  453. // ...
  454. }
  455. ```
  456. See also: [Class: GlobalScope](https://flowgram.ai/auto-docs/editor/classes/GlobalScope.html)