mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
252 lines
10 KiB
Plaintext
252 lines
10 KiB
Plaintext
# 消费变量
|
||
|
||
在 Flowgram 中,变量的管理和消费是构建动态、可交互应用的核心。理解如何有效地消费变量,对于开发者来说至关重要。这篇文档将带你深入了解在不同场景下消费变量的各种方式。
|
||
|
||
## 在节点内获取变量树
|
||
|
||
在画布的节点中,我们常常需要获取当前作用域下可用的变量,并将它们以树形结构展示出来,方便用户进行选择和操作。`useAvailableVariables` 这个 React Hook 就是为此而生。
|
||
|
||
### `useAvailableVariables`
|
||
|
||
`useAvailableVariables` 是一个轻量级的 Hook,它直接返回当前作用域可用的变量数组 (`VariableDeclaration[]`)。如果你只需要一个简单的变量列表,并且不需要进行更复杂的操作,那么 `useAvailableVariables` 会是更便捷的选择。
|
||
|
||
```tsx pure title="use-variable-tree.tsx"
|
||
import {
|
||
type BaseVariableField,
|
||
useAvailableVariables,
|
||
} from '@flowgram.ai/fixed-layout-editor';
|
||
|
||
// .... 在 React 组件或 Hook 中
|
||
|
||
const availableVariables = useAvailableVariables();
|
||
|
||
const renderVariable = (variable: BaseVariableField) => {
|
||
// 这里可以根据你的需求渲染每个变量
|
||
// ....
|
||
}
|
||
|
||
return availableVariables.map(renderVariable);
|
||
|
||
// ....
|
||
```
|
||
|
||
### 获取 Object 类型变量的下钻
|
||
|
||
当变量的类型是 `Object` 时,我们往往需要能够“下钻”到它的内部,获取其属性。`ASTMatch.isObject` 方法可以帮助我们判断一个变量类型是否为对象。如果是,我们就可以递归地渲染它的 `properties`。
|
||
|
||
```tsx pure title="use-variable-tree.tsx"
|
||
import {
|
||
type BaseVariableField,
|
||
ASTMatch,
|
||
} from '@flowgram.ai/fixed-layout-editor';
|
||
|
||
// ....
|
||
|
||
const renderVariable = (variable: BaseVariableField) => ({
|
||
title: variable.meta?.title,
|
||
key: variable.key,
|
||
// 只有 Object 类型的变量才可以下钻
|
||
children: ASTMatch.isObject(variable.type) ? variable.type.properties.map(renderVariable) : [],
|
||
});
|
||
|
||
// ....
|
||
|
||
```
|
||
|
||
### 获取 Array 类型变量的下钻
|
||
|
||
与 `Object` 类型类似,当遇到 `Array` 类型的变量时,我们也希望能展示它的内部结构。对于数组,我们通常关心的是其元素的类型。`ASTMatch.isArray` 可以判断变量类型是否为数组。值得注意的是,数组的元素类型可能是任意的,甚至可能是另一个数组。因此,我们需要一个递归的辅助函数 `getTypeChildren` 来处理这种情况。
|
||
|
||
```tsx pure title="use-variable-tree.tsx"
|
||
import {
|
||
type BaseVariableField,
|
||
type BaseType,
|
||
ASTMatch,
|
||
} from '@flowgram.ai/fixed-layout-editor';
|
||
|
||
// ....
|
||
|
||
const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
|
||
if (!type) return [];
|
||
|
||
// 获取 Object 的属性
|
||
if (ASTMatch.isObject(type)) return type.properties;
|
||
|
||
// 递归获取 Array 的元素类型
|
||
if (ASTMatch.isArray(type)) return getTypeChildren(type.items);
|
||
|
||
return [];
|
||
};
|
||
|
||
const renderVariable = (variable: BaseVariableField) => ({
|
||
title: variable.meta?.title,
|
||
key: variable.key,
|
||
children: getTypeChildren(variable.type).map(renderVariable),
|
||
});
|
||
|
||
// ....
|
||
|
||
```
|
||
|
||
## 官方物料:`VariableSelector`
|
||
|
||
为了让你能更轻松地在应用中集成变量选择的功能,我们为你准备了官方物料 —— `VariableSelector` 组件。它封装了前面提到的所有逻辑,让你无需从头开始,即可拥有一个功能强大、界面美观的变量选择器。
|
||
|
||
<img loading="lazy" src="/materials/variable-selector.png" style={{width:500}}/>
|
||
|
||
`VariableSelector` 不仅支持展示变量树,还内置了搜索、过滤等高级功能,可以极大地提升用户体验。更详细的介绍,请参考 [官方表单物料](/guide/advanced/form-materials.html) 文档。
|
||
|
||
你可以通过以下两种方式来使用它:
|
||
|
||
**1. 通过 NPM 包引用**
|
||
|
||
这是最简单直接的方式,只需一行代码,即可在你的项目中引入 `VariableSelector`。
|
||
|
||
```tsx
|
||
import { VariableSelector } from '@flowgram.ai/form-materials';
|
||
```
|
||
|
||
**2. 通过 CLI 复制源代码**
|
||
|
||
如果你希望对 `VariableSelector` 进行更深度的定制,我们也提供了通过 CLI 将组件源代码直接复制到你的项目中的方式。这样,你就可以随心所欲地修改它,以满足你独特的业务需求。
|
||
|
||
```bash
|
||
npx @flowgram.ai/materials components/variable-selector
|
||
```
|
||
|
||
## `ScopeAvailableData`:你的变量百宝箱
|
||
|
||
`ScopeAvailableData` 对象是变量系统的核心之一,它由 `useScopeAvailable` Hook 返回,是你与作用域内可用变量进行交互的主要桥梁。你可以把它想象成一个功能强大的“变量工具箱”。
|
||
|
||
### `useScopeAvailable`
|
||
|
||
`useScopeAvailable` 是一个功能更强大的 Hook,它能够返回一个 `ScopeAvailableData` 对象,其中不仅包含了当前作用域所有可用的变量信息,还提供了一些高级 API,比如 `trackByKeyPath`。
|
||
|
||
它与 `useAvailableVariables` 的主要区别在于:
|
||
|
||
* **返回值不同**:`useAvailableVariables` 直接返回变量数组,而 `useScopeAvailable` 返回的是一个包含了 `variables` 属性以及其他方法的 `ScopeAvailableData` 对象。
|
||
* **适用场景**:当你需要对变量进行更复杂的操作,比如追踪单个变量的变化时,`useScopeAvailable` 是你的不二之选。
|
||
|
||
```tsx
|
||
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
|
||
|
||
const available = useScopeAvailable();
|
||
|
||
// available 对象上包含了变量列表和其他 API
|
||
console.log(available.variables);
|
||
```
|
||
|
||
|
||
### 获取变量列表
|
||
|
||
最基础的用法就是获取当前作用域下所有可用的变量。
|
||
|
||
```tsx
|
||
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
|
||
|
||
function MyComponent() {
|
||
const available = useScopeAvailable();
|
||
|
||
// available.variables 就是一个包含了所有可用变量的数组
|
||
console.log(available.variables);
|
||
|
||
return (
|
||
<ul>
|
||
{available.variables.map(variable => (
|
||
<li key={variable.key}>{variable.name}</li>
|
||
))}
|
||
</ul>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 追踪单个变量的变化:`trackByKeyPath`
|
||
|
||
当你只关心某个特定变量(尤其是嵌套在 Object 或 Array 中的变量)的变化时,`trackByKeyPath` 就派上用场了。它能让你精准地“订阅”这个变量的更新,而不会因为其他不相关变量的变化导致组件重新渲染,从而实现更精细的性能优化。
|
||
|
||
假设我们有一个名为 `user` 的 Object 类型变量,它有一个 `name` 属性。我们希望在 `user.name` 变化时更新组件。
|
||
|
||
```tsx
|
||
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
|
||
import { useEffect, useState } from 'react';
|
||
|
||
function UserNameDisplay() {
|
||
const available = useScopeAvailable();
|
||
const [userName, setUserName] = useState('');
|
||
|
||
useEffect(() => {
|
||
// 定义我们要追踪的变量路径
|
||
const keyPath = ['user', 'name'];
|
||
|
||
// 开始追踪!
|
||
const disposable = available.trackByKeyPath(keyPath, (nameField) => {
|
||
// 当 user.name 变量字段变化时,这个回调函数会被触发
|
||
// nameField 就是那个变化的变量字段,我们可以从中获取最新的默认值
|
||
setUserName(nameField?.meta.default || '');
|
||
});
|
||
|
||
// 组件卸载时,别忘了取消追踪,避免内存泄漏
|
||
return () => disposable.dispose();
|
||
}, [available]); // 依赖项是 available 对象
|
||
|
||
return <div>User Name: {userName}</div>;
|
||
}
|
||
```
|
||
|
||
### 高级事件监听 API
|
||
|
||
除了 `trackByKeyPath`,`ScopeAvailableData` 还提供了一套更底层的事件监听 API,让你能够更精细地控制变量变化的响应逻辑。这在处理一些复杂的、需要手动管理订阅的场景时非常有用。
|
||
|
||
下面我们通过一个表格来详细对比这三个核心的监听 API:
|
||
|
||
| API | 触发时机 | 回调参数 | 核心区别与适用场景 |
|
||
| :--- | :--- | :--- | :--- |
|
||
| `onVariableListChange` | 当可用变量的**列表结构**发生变化时。 | `(variables: VariableDeclaration[]) => void` | **只关心列表本身**。比如,上游节点新增/删除了一个输出变量,导致可用变量的总数或成员发生了变化。它不关心变量内部和下钻的改变。适用于需要根据变量列表的有无或数量来更新 UI 的场景。 |
|
||
| `onAnyVariableChange` | 当列表中**任意一个**变量的**类型,元数据和下钻字段**发生变化时。 | `(changedVariable: VariableDeclaration) => void` | **只关心变量内容的更新**。比如,用户修改了一个输出变量的类型。它不关心列表结构的变化。适用于需要对任何一个变量的内容变化做出反应的场景。 |
|
||
| `onListOrAnyVarChange` | 以上两种情况**任意一种**发生时。 | `(variables: VariableDeclaration[]) => void` | **最全面的监听**,是前两者的结合。无论是列表结构变化,还是任何一个变量的变化,都会触发。适用于需要对任何可能的变化都进行响应的“兜底”场景。 |
|
||
|
||
#### 代码示例
|
||
|
||
让我们通过一个具体的例子来看看如何在组件中使用这些 API。
|
||
|
||
```tsx
|
||
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
|
||
import { useEffect } from 'react';
|
||
|
||
function AdvancedListenerComponent() {
|
||
const available = useScopeAvailable();
|
||
|
||
useEffect(() => {
|
||
// 1. 监听列表结构变化
|
||
const listChangeDisposable = available.onVariableListChange((variables) => {
|
||
console.log('可用变量列表的结构变了!新的列表长度是:', variables.length);
|
||
});
|
||
|
||
// 2. 监听任意变量的变化
|
||
const valueChangeDisposable = available.onAnyVariableChange((changedVariable) => {
|
||
console.log(`变量 '${changedVariable.keyPath.join('.')}' 的类型、下钻或者 meta 变了`);
|
||
});
|
||
|
||
// 3. 监听所有变化(结构或单个变量内部)
|
||
const allChangesDisposable = available.onListOrAnyVarChange((variables) => {
|
||
console.log('变量列表或其中某个变量发生了变化!');
|
||
// 注意:这里的回调参数是完整的变量列表,而不是单个变化的变量
|
||
});
|
||
|
||
// 在组件卸载时,务必清理所有的监听器,防止内存泄漏
|
||
return () => {
|
||
listChangeDisposable.dispose();
|
||
valueChangeDisposable.dispose();
|
||
allChangesDisposable.dispose();
|
||
};
|
||
}, [available]);
|
||
|
||
return <div>请在控制台查看变量变化的日志...</div>;
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
|
||
* 这些 API 返回的都是一个 `Disposable` 对象。
|
||
* 为了避免内存泄漏和不必要的计算,你必须在 `useEffect` 的清理函数中调用其 `dispose()` 方法来取消监听。
|