variable-output.mdx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. # 输出变量
  2. 我们主要将输出变量分为三类:
  3. 1. **输出节点变量**:通常作为该节点的产出,供后续节点使用。
  4. 2. **输出节点私有变量**:输出变量仅限于节点内部(包括子节点),不能被外部节点访问。
  5. 3. **输出全局变量**:贯穿整个流程,任何节点都可以读取,适合存放一些公共状态或配置。
  6. ## 输出节点变量
  7. 输出节点变量,意味着这个变量和当前节点的生命周期是绑定的。当节点被创建时,变量就诞生了;当节点被删除时,变量也随之消失。
  8. 我们通常有三种方式来输出节点变量:
  9. ### 方式一:通过表单副作用同步
  10. [表单副作用](/guide/form/form#副作用-effect) 通常在节点的 `form-meta.ts` 文件中进行配置,是定义节点输出变量最常见的方式。
  11. #### `provideJsonSchemaOutputs` 物料
  12. 若节点所需输出变量的结构与 [JSON Schema](https://json-schema.org/) 结构匹配,即可使用 `provideJsonSchemaOutputs` 这一副作用(Effect)物料。
  13. 只需要在 `formMeta` 的 `effect` 中加上两行配置即可:
  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, // 变量标题自动同步
  22. outputs: provideJsonSchemaOutputs,
  23. },
  24. };
  25. ```
  26. #### 通过 `createEffectFromVariableProvider` 自定义输出
  27. `provideJsonSchemaOutputs` 只适配 `JsonSchema`。如果你想要定义自己的一套 Schema,那么就需要自定义表单的副作用。
  28. :::note
  29. Flowgram 提供了 `createEffectFromVariableProvider`,只需要定义一个 `parse`函数,就可以自定义自己的变量同步副作用:
  30. - `parse` 会在表单值初始化和更新时被调用
  31. - `parse` 的输入为当前字段的表单的值
  32. - `parse` 的输出为变量 AST 信息
  33. :::
  34. 下面这个例子中,我们为表单的两个字段 `path.to.value` 和 `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. #### 多个表单字段同步到一个变量上
  92. 如果多个字段同步到一个变量,就需要用到 `createEffectFromVariableProvider` 的 `namespace` 字段,将多个字段的变量数据同步到同一个命名空间上。
  93. ```tsx pure title="form-meta.ts"
  94. import {
  95. createEffectFromVariableProvider,
  96. ASTFactory,
  97. } from '@flowgram.ai/fixed-layout-editor';
  98. /**
  99. * 从 form 拿到多个字段的信息
  100. */
  101. const variableSyncEffect = createEffectFromVariableProvider({
  102. // 必须添加,确保不同字段的副作用,同步到同一个命名空间上
  103. namespace: 'your_namespace',
  104. // 将表单值解析为变量
  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. #### 在副作用中使用 `node.scope` API
  129. 如果 `createEffectFromVariableProvider` 不能满足你的需求,你也可以直接在表单副作用中使用 `node.scope` API,进行更加灵活多变的变量操作。
  130. :::note
  131. `node.scope` 会返回一个节点的变量作用域(Scope)对象,这个对象上挂载了几个核心方法:
  132. - `setVar(variable)`: 设置一个变量。
  133. - `setVar(namespace, variable)`: 在指定的命名空间下设置一个变量。
  134. - `getVar()`: 获取所有变量。
  135. - `getVar(namespace)`: 获取指定命名空间下的变量。
  136. - `clearVar()`: 清空所有变量。
  137. - `clearVar(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("查看生成的变量", 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("查看生成的变量", context.node.scope.getVar('namespace_2'))
  174. }) as Effect,
  175. }],
  176. },
  177. render: () => (
  178. // ...
  179. )
  180. }
  181. ```
  182. ### 方式二:通过插件同步变量
  183. 除了在表单中静态配置,我们还可以在插件(Plugin)中,通过 `node.scope` 更新自由动态地操作节点的变量。
  184. #### 指定节点的 Scope 进行更新
  185. 下面的例子演示了如何在插件的 `onInit`生命周期中,获取开始节点的 `Scope`,并对它的变量进行一系列操作。
  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. #### 在 onNodeCreate 同步变量
  211. 下面的例子演示了如何通过 `onNodeCreate` 获取到新创建节点的 Scope,并通过监听 `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. ### 方式三:在 UI 中同步变量(不推荐)
  250. :::warning
  251. 直接在 UI 中同步变量(方式三)是一种非常不推荐的做法。
  252. 它会打破**数据和渲染分离**的原则,会导致数据和渲染之间的紧密耦合:
  253. - **脱离 UI 无法同步变量**:脱离了 UI 就无法独立更新变量,会导致数据和渲染之间的不一致。
  254. - **增加代码复杂度**:直接在 UI 中操作变量,会增加 UI 逻辑的复杂性,使代码更难维护。
  255. - **性能问题**:变量的同步操作可能会触发 UI 组件的不必要的重渲染。
  256. :::
  257. 下面的例子演示了如何在 `formMeta.render` 中,通过 `useCurrentScope` 事件,同步更新变量。
  258. ```tsx pure title="form-meta.ts"
  259. import {
  260. createEffectFromVariableProvider,
  261. ASTFactory,
  262. } from '@flowgram.ai/fixed-layout-editor';
  263. /**
  264. * 从 form 拿到多个字段的信息
  265. */
  266. const FormRender = () => {
  267. /**
  268. * 获取到当前作用域,用于后续设置变量
  269. */
  270. const scope = useCurrentScope()
  271. return <>
  272. <UserCustomForm
  273. onValuesChange={(values) => {
  274. scope.setVar(
  275. ASTFactory.createVariableDeclaration({
  276. meta: {
  277. title: values.title,
  278. },
  279. key: `uid`,
  280. type: ASTFactory.createString(),
  281. })
  282. )
  283. }}
  284. />
  285. </>
  286. }
  287. export const formMeta = {
  288. render: () => <FormRender />
  289. }
  290. ```
  291. ## 输出节点私有变量
  292. 私有变量是指只能在当前节点及其子节点中访问的变量。
  293. 私有变量可以通过私有作用域 `node.privateScope` 来设置和获取,它的作用域链关系如下图所示:
  294. ```mermaid
  295. graph BT
  296. subgraph 当前节点
  297. 子节点_1.scope -.依赖变量.-> 当前节点.privateScope
  298. 当前节点.scope -.依赖变量.-> 当前节点.privateScope
  299. 子节点_2.scope -.依赖变量.-> 当前节点.privateScope
  300. end
  301. 当前节点 -.都依赖变量.-> 上游节点.scope
  302. 下游节点.scope -.依赖变量.-> 当前节点.scope
  303. 下游节点.scope -.依赖变量.-> 上游节点.scope
  304. style 当前节点.privateScope fill:#f9f,stroke:#333,stroke-width:3px
  305. style 当前节点.scope stroke:#333,stroke-width:3px
  306. ```
  307. 下面只列举其中两种方式,其他方式可以根据[输出节点变量](#输出节点变量)的方式类推
  308. ### 方式一:通过 `createEffectFromVariableProvider`
  309. `createEffectFromVariableProvider` 提供了参数 `scope`,用于指定变量的作用域。
  310. - `scope` 设置为 `private` 时,变量的作用域为当前节点的私有作用域 `node.privateScope`
  311. - `scope` 设置为 `public` 时,变量的作用域为当前节点的作用域 `node.scope`
  312. ```tsx pure title="form-meta.ts"
  313. import {
  314. createEffectFromVariableProvider,
  315. ASTFactory,
  316. } from '@flowgram.ai/fixed-layout-editor';
  317. export const formMeta = {
  318. effect: {
  319. // Create variable in privateScope
  320. // = node.privateScope.setVar('path.to.value', ASTFactory.createVariableDeclaration(parse(v)))
  321. 'path.to.value': createEffectFromVariableProvider({
  322. scope: 'private',
  323. // parse form value to variable
  324. parse(v: string) {
  325. return {
  326. meta: {
  327. title: `Your Private Variable Title`,
  328. },
  329. key: `uid_${node.id}_locals`,
  330. type: createTypeFromValue(v)
  331. }
  332. }
  333. }),
  334. },
  335. render: () => (
  336. // ...
  337. )
  338. }
  339. ```
  340. ### 方式二:通过 `node.privateScope`
  341. `node.privateScope` 的 API 设计得和节点作用域(`node.scope`)几乎一模一样,都提供了 `setVar`、`getVar`、`clearVar`等方法,并且同样支持命名空间(namespace)。详情可以参考 [`node.scope`](#在副作用中使用-nodescope-api)。
  342. ```tsx pure title="form-meta.tsx"
  343. import { Effect } from '@flowgram.ai/editor';
  344. export const formMeta = {
  345. effect: {
  346. 'path.to.value': [{
  347. event: DataEvent.onValueInitOrChange,
  348. effect: ((params) => {
  349. const { context, value } = params;
  350. context.node.privateScope.setVar(
  351. ASTFactory.createVariableDeclaration({
  352. meta: {
  353. title: `Your Private Variable Title`,
  354. },
  355. key: `uid_${node.id}`,
  356. type: createTypeFromValue(value),
  357. })
  358. )
  359. console.log("查看生成的变量", context.node.privateScope.getVar())
  360. }) as Effect,
  361. }],
  362. },
  363. render: () => (
  364. // ...
  365. )
  366. }
  367. ```
  368. ## 输出全局变量
  369. 全局变量就像是整个流程的“共享内存”,任何节点、任何插件都可以访问和修改它。它非常适合用来存储一些贯穿始终的状态,比如用户信息、环境配置等等。
  370. 和节点变量类似,我们也有两种主要的方式来获取全局变量的作用域(`GlobalScope`)。
  371. ### 方式一:在插件中获取
  372. 在插件的上下文中(`ctx`),我们可以直接“注入”`GlobalScope` 的实例:
  373. ```tsx pure title="global-variable-plugin.tsx"
  374. import {
  375. GlobalScope,
  376. definePluginCreator,
  377. PluginCreator
  378. } from '@flowgram.ai/fixed-layout-editor';
  379. export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
  380. definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
  381. onInit(ctx, options) {
  382. const globalScope = ctx.get(GlobalScope)
  383. globalScope.setVar(
  384. ASTFactory.createVariableDeclaration({
  385. meta: {
  386. title: `Your Output Variable Title`,
  387. },
  388. key: `your_variable_global_unique_key`,
  389. type: ASTFactory.createString(),
  390. })
  391. )
  392. }
  393. })
  394. ```
  395. ### 方式二:在 UI 中获取
  396. 如果你想在画布的 React 组件中与全局变量交互,可以使用 `useService` 这个 Hook 来获取 `GlobalScope` 的实例:
  397. ```tsx pure title="global-variable-component.tsx"
  398. import {
  399. GlobalScope,
  400. useService,
  401. } from '@flowgram.ai/fixed-layout-editor';
  402. function GlobalVariableComponent() {
  403. const globalScope = useService(GlobalScope)
  404. // ...
  405. const handleChange = (v: string) => {
  406. globalScope.setVar(
  407. ASTFactory.createVariableDeclaration({
  408. meta: {
  409. title: `Your Output Variable Title`,
  410. },
  411. key: `uid_${v}`,
  412. type: ASTFactory.createString(),
  413. })
  414. )
  415. }
  416. return <Input onChange={handleChange}/>
  417. }
  418. ```
  419. ### 全局作用域的 API
  420. `GlobalScope` 的 API 设计得和节点作用域(`node.scope`)几乎一模一样,都提供了 `setVar`、`getVar`、`clearVar` 等方法,并且同样支持命名空间(namespace)。详情可以参考 [`node.scope`](#在副作用中使用-nodescope-api)。
  421. 下面是一个在插件中操作全局变量的综合示例:
  422. ```tsx pure title="sync-variable-plugin.tsx"
  423. import {
  424. GlobalScope,
  425. } from '@flowgram.ai/fixed-layout-editor';
  426. // ...
  427. onInit(ctx, options) {
  428. const globalScope = ctx.get(GlobalScope);
  429. // 1. Create, Update, Read, Delete Variable in GlobalScope
  430. globalScope.setVar(
  431. ASTFactory.createVariableDeclaration({
  432. meta: {
  433. title: `Your Output Variable Title`,
  434. },
  435. key: `your_variable_global_unique_key`,
  436. type: ASTFactory.createString(),
  437. })
  438. )
  439. console.log(globalScope.getVar())
  440. globalScope.clearVar()
  441. // 2. Create, Update, Read, Delete Variable in GlobalScope's namespace: 'namespace_1'
  442. globalScope.setVar(
  443. 'namespace_1',
  444. ASTFactory.createVariableDeclaration({
  445. meta: {
  446. title: `Your Output Variable Title 2`,
  447. },
  448. key: `uid_2`,
  449. type: ASTFactory.createString(),
  450. })
  451. )
  452. console.log(globalScope.getVar('namespace_1'))
  453. globalScope.clearVar('namespace_1')
  454. // ...
  455. }
  456. ```
  457. 详见:[Class: GlobalScope](https://flowgram.ai/auto-docs/editor/classes/GlobalScope.html)