普通视图

发现新文章,点击刷新页面。
今天 — 2025年9月15日首页

低代码设计态变量挑战与设计 — 前端变量引擎介绍

作者 FlowGramAI
2025年9月15日 15:12

⚠️ 注意:本文中的“变量引擎”,为 流程设计态 变量引擎,不涉及流程运行态

flowgram 变量引擎源代码详见:github.com/bytedance/f…

1. 变量,低代码的“神经系统”

今天想和大家分享一个话题:flowgram 变量引擎。 在开始深入技术细节之前,我们先用一个通俗的类比来聊聊“变量”到底是什么。想象一下,我们正在搭建一个复杂的乐高模型,比如一个机器人。这个机器人由很多模块组成:手臂、腿、头部等等。如果我们希望这些模块能够协同工作——比如,当头部转动时,手臂也跟着挥舞——我们就需要在它们之间建立一种信息传递的机制。

在这个场景里,“变量”就扮演着这个信息传递者的角色。它就像连接各个模块的“神经”或者“电路”,告诉一个模块另一个模块发生了什么变化。 在低代码平台(如 flowgram)中,每个功能节点(比如“获取数据”、“处理文本”、“调用 API”)都像是乐高模型中的一个模块。而变量,就是让这些独立的节点能够互相“沟通”,将数据从一个节点传递到另一个节点的关键所在。 变量就是节点间信息传递的载体。没有它,整个流程(Flow)就是一盘散沙,无法形成有意义的应用。

2. 痛点:低代码设计态中的变量难点

既然变量如此重要,那么在一个低代码平台前端的开发过程中,我们遇到了哪些挑战呢? 随着业务逻辑变得越来越复杂,我们发现变量变得 限制复杂、信息复杂、联动复杂,具体体现为以下几个问题:

2.1. 变量的可见性(可选性)

并非所有变量都应该对所有节点可见。例如,一个循环节点内部产生的临时变量(如 item 和 index),就不应该被循环外的节点访问到。如何精确地控制每个节点能“看到”和使用哪些变量?这就是“作用域”的问题。 变量引擎能够精确控制每个变量的有效范围(即作用域)。如同为不同房间配置专属钥匙,它确保了变量只在预期的节点中被访问,从而有效避免了数据污染和意外的逻辑错误。

Start 节点定义的 query 变量,在它后面的 LLM 和 End 节点都能轻松访问

LLM 节点在一个 Condition 分支里,像是在一个独立的房间。外面的 End 节点自然就拿不到它的 result 变量了。

2.2. 变量结构信息复杂

变量是个复杂的多层级结构哈:

  • 变量 Object 能往下深挖好几个子字段,子字段还能定义成 Object,接着再定义往下深挖的字段
  • 有些类型能互相嵌套,就像 Array<Map<String, Array>> 这样

    变量引擎能让你轻松定制和管理这些结构

    在这张图里,你能看到所有节点的输出变量,还有它们之间的层级关系

    2.3. 类型自动推导

    有时,一个变量的类型依赖于另一个或多个变量的计算结果。 例如,C = A + B。当 A 或 B 的类型发生变化时,C 的类型应该能够自动、响应式地更新。如何优雅地实现这种“级联反应”? 通过变量引擎,可以通过 简单的变量声明定义,变量引擎就会根据上下文自动联动推导出它的类型。

    例如,当 Start 节点中 arr 变量的类型发生变更时,Batch 节点输出的 item 类型也会自动同步更新,确保了类型的一致性。

    3. 核心概念:作用域/作用域链/AST

    想像这样一个变量引擎的世界:

    • 通过一个个 作用域 来划定出一个个 国家
    • 每个国家包含三大公民:声明、类型、表达式
    • 国家与国家之间通过 作用域链 来实现交流

    3.1. 作用域与作用域链

    作用域的具体划定,可以由不同的业务来确定:

    流程图里,作用域通常约定为一个节点

    低代码页面编辑器,作用域可以约定为一个组件(含变量)

    侧边栏里面的全局变量,也是一个作用域

    我们将作用域类比为国家,作用域链类比为贸易链,可以获得以下的概念类比: 国家 + 贸易链:

    国家之间通过贸易链,进行商品进出口

    贸易链规定:

    1. 可以从哪些国家进口

    2. 又可以出口到哪些国家

    作用域 + 作用域链:

    作用域之间通过作用域链,进行变量的消费与输出

    作用域链规定了

    1. 作用域依赖:可以从哪些作用域消费变量
    2. 作用域覆盖:又可以输出变量到哪些作用域
    abstract class ScopeChain {
        abstract getCovers(scope)
        abstract getDeps(scope)
    }
    

    ❓ 为什么要在节点之外,新抽象一个作用域的概念?

    • 节点 !== 作用域
    • 全局作用域(GlobalScope)和节点无关
    • 一个节点可以存在多个作用域

    3.2. 变量引擎的三大公民

    声明

    声明 = Identifier + Definition

    商品也是一种声明,和变量类似

    JavaScript 中的变量,唯一 Key + 变量的值

    低代码平台中的变量,唯一Key(apiName)+ 变量定义

    声明 通过 Indentifier,被其他作用域给消费

    Global 作用域 的 结构体声明 被 Start 节点消费, Start 节点的变量被 LLM 节点的 fString 表达式消费, LLM 节点通过下钻,实际消费了 Global 作用域中的 Property_A 声明

    类型

    类型(typing,又称类型指派)赋予一组比特某个意义。 来自 Wikipedia

    变量引擎中,所有变量的声明,都是围绕着类型建立的。 变量引擎内置了以下类型:String、Number、Integer、Boolean、Object、Array、Map

    类型可以相互嵌套:

    • 如:Array<Map<String, Array<Array>>>

    • Object 类型 和 属性声明 可以相互嵌套:

    Object 类型 和 属性声明 可以相互嵌套

    表达式

    设计态变量引擎中的表达式,可以通过特定方式 组合 若干个 声明好的变量,并 返回一个变量类型:

    例如:

    JsonPath 表达式

    Python 表达式

    3.3. AST Is All You Need

    核心思想:流程编排的本质就是一种可视化的编程语言。 Hint:可以通过 ts-ast-viewer.com/ 更加深入理解 AST

    三大公民 “声明”、“表达式”、“类型” 在设计上都有一些通用点:

    1. 三大公民各自有 非常多的类别,且业务有扩展诉求
    • 声明:变量声明、自定义类型声明、Interface 声明 等
    • 类型:String、Number、Array、Map、Interface 等
    • 表达式:KeyPath、JsonPath、Python、JS 等
    1. 三大公民相互之间可以 组织成一棵树的结构
    • 变量声明
    • Object
    • 属性声明
    • Array
    • String
    • 属性声明
    • Number
    • 属性声明
    • JsonPath 表达
    1. 三大公民都有 监听 的诉求

    Condition 类型联动操作符

    变量引擎,定义了 ASTNode 来解决这三个问题:

    • AST:通过树的形式,组合 ASTNode,来描述变量信息

    • ASTNode:AST 中 可扩展、可组合、可监听 的协议节点

    继承 ASTNode 可以定义非常多的类型

    class StringType extends ASTNode {}
    
    class MapType extends ASTNode {}
    
    class VariableDeclaration extends ASTNode {}
    
    class StructDeclaration extends ASTNode {}
    
    class KeyPathExpression extends ASTNode {}
    
    class JsonPathExpression extends ASTNode {}
    
    // 业务扩展 AST 节点
    class CustomType extends ASTNode {}
    
    class CustomType extends BaseExpression {}
    class CustomType extends BaseType {}
    

    ASTNode 之间 组成了一棵树的结构

    createVariableDeclaration({
        key: 'variable_xxx'
        type: createObject({
            properties: [
                createProperty({  
                    key: 'a', 
                    type: createString() 
                }),
                createProperty({
                    key: 'b',
                    initializer: createJsonPathExpression({
                        path: '$.start.outputs.query'
                    })
                })
            ]
        })
    })
    

    业务可监听任意 ASTNode 的变化

    // 监听变量变化
    variable.subscribe(() => {
        // do something
    })
    
    // 监听变量的 Name 变化
    variable.subscribe(() => {
        // do something
    }, {
        selector: (_v) => _v.meta.name
    })
    
    // 监听变量类型变化
    variable.onTypeChange(() => {
        // do something
    });
    

    参考 TypeScript AST 的定义,我们用 AST Schema 可以用来描述变量的 AST 信息:

    TypeScript AST 定义示范

    AST Schema 在 TypeScript AST 的基础上做了简化:

    createVariableDeclaration({
        key: 'variable_xxx'
        type: createObject({
            properties: [
                createProperty({  key: 'a', type: createString() }),
                createProperty({
                    key: 'b',
                    initializer: createJsonPathExpression({
                        path: '$.start.outputs.query'
                    })
                })
            ]
        })
    })
    

    聊到 AST Schema ,不避免会将其与 Json Schema 进行比较:

    Json Schema 和 AST Schema 要解决的问题是不同的

    • Json Schema 只能描述类型,AST Schema 可以表达 “声明”、“表达式”、“类型” 三大公民
    • AST Schema 中每一个节点都可以对应到一个实际存在的 AST 节点,但是 Json Schema 不行
    • Json Schema 相比 AST Schema 在团队沟通上更有优势

    两者的应用场景也不同

    • 变量引擎内核 需要更强大的扩展与表达能力,因此需要用 AST Schema 来描述更丰富的变量信息,包括类型联动信息

    • 上层业务 需要协议更通用更易于沟通,因此可以使用 Json Schema 来降低上手成本,方便前后端沟通,并且复用生态(如 zod)

    4. 架构:flowgram 变量引擎整体设计

    易用性和可扩展性的平衡一直是架构设计上一大难题。 flowgram 变量引擎通过三大分层架构兼顾了易用性和可扩展性:

    flowgram 变量引擎分层架构

    变量引擎设计上遵循 DIP(依赖反转)原则,按照 代码稳定性、抽象层次 以及和 业务的远近 分为三层:

    • 变量抽象层:变量架构中抽象层次最高,代码也最为稳定的部分。抽象层对 AST、Scope、ScopeChain 等核心概念进行了抽象类定义。

    • 变量实现层:变量架构中变动较大,不同业务之间可能存在调整的部分。引擎内置了一批较为稳定的 AST 节点和 ScopeChain 的实现。当用户存在复杂的变量需求时,可以通过依赖注入注册新的 AST 或者重载已有 AST 节点实现定制化。

    • 变量物料层:基于外观模式(Facade)的思路提高变量易用性,将复杂的变量逻辑封装为简单开箱即用的变量物料

    5. 附录:flowgram 变量底层部分 API 简介

    5.1. 变量抽象层部分 API 简介

    获取变量引擎

    import { useService } from "@flowgram.ai/core";
    import { VariableEngine } from "@flowgram.ai/variable-core";
    
    const variableEngine = useService(VariableEngine);
    

    Scope CURD

    // 创建 Scope
    const globalScope = variableEngine.createScope('global')
    
    // 创建 Scope,并带上 meta 信息
    const loopNodePublicScope = variableEngine.createScope('loop-node-public', { 
        node: loopNode,
        type: 'public' 
    })
    
    // 获取 Scope
    const scope = variableEngine.getScopeById('loop-node-public')
    scope.meta.type === 'public'
    
    // 获取全量的 Scope
    variableEngine.getAllScopes()
    
    // 删除 Scope
    variableEngine.removeScopeById('global')
    

    Scope 的包含依赖和覆盖作用域

    const scope = variableEngine.getScopeById('test')
    
    // 获取依赖作用域
    console.log(scope.depScopes)
    
    // 获取覆盖作用域
    console.log(scope.coverScopes);
    

    ASTNodeJSON 通常表现为一个包含 kind 字段的 Plain Object

    • kind 字段用于表示当前 ASTNode 节点的类别
    • ASTNodeJSON 可以相互嵌套形成树的结构
    {
        kind: ASTKind.VariableDeclaration,
        key: 'variableKey111',
        meta: { name: 'xxx' },
        type: {
            ASTKind.String
        }
    }
    

    上面的 ASTNodeJSON 描述了一个变量声明:

    • kind 为 VariableDeclaration 表明了这是一个描述变量信息的 ASTNode
    • 该变量声明的 type 是当前变量声明的子节点,也用 ASTNodeJSON 来描述
    • type 中的 kind为 String,表明这个 ASTNode 存储了一个为 String 的变量类型

    ❗️概念澄清 ASTNodeJSON 和 ASTNode 的关系,类似于 React 中 JSX 和 VDOM 的关系

    ASTNode 存储在 Scope 中,Scope 中可以对 ASTNode 进行 CRUD:

    import { ASTKind } from "@flowgram.ai/variable-core";
    
    // 在 namespace 1 中存储一个新变量,变量类型为 String
    scope.ast.set('namespace1', {
        kind: ASTKind.VariableDeclaration,
        key: 'new-variable',
        type: {
            kind: ASTKind.String,
        }
    })
    
    // 在 namespace 2 中存储一个表达式,引用 a 变量下钻的 b 字段
    scope.ast.set('namespace2', {
        kind: ASTKind.KeyPathExpression,
        keyPath: ['a', 'b']
    })
    
    // 更新 namespace 2 中的表达式引用到 c 变量下钻的 d 字段
    scope.ast.set('namespace2', {
        kind: ASTKind.KeyPathExpression,
        keyPath: ['c', 'd']
    })
    
    // 获取 namespace1 中的 ASTNode
    const astNode = scope.ast.get('namespace1');
    
    // 通过 fromJSON 更新变量中的类型信息
    astNode.fromJSON({
        type: {
            kind: ASTKind.Number,
        }
    });
    
    // 通过 toJSON 转化回 ASTNodeJSON
    astNode.toJSON();
    
    
    // 删除 namespace2 存储的 ASTNode
    scope.ast.remove('namespace1');
    

    ASTNode 基于 Rxjs 提供了强大的监听能力:

    const astNode = scope.ast.get('test');
    
    // 监听 ASTNode 节点的变化
    const disposable = astNode.subscribe((_node) => {
        // do something to node
    });
    
    // 监听 ASTNode 节点中 name 信息的变化
    const disposable = astNode.subscribe((_name) => {
        // do something to name
    },  { selector: _node => _node.meta.name });
    
    // 将一个 AnimationFrame 中所有该 AST 的变化 debounce 成一个
    const disposable = astNode.subscribe((_node) => {
        // do something to node
    }, { debounceAnimation: true });
    
    // 进阶用法:直接拿到当前 ASTNode 的 rx observable,通过自由 rx 操作符实现更复杂的异步处理逻辑
    const ob$ = astNode.value$.pipe(
        switchMap(...),
        throttle(...),
        takeUntil(...)
    )
    

    ASTNode 本质上是个抽象类,所有变量存储单元的实现都需要通过继承 ASTNode 进行

    假设我们需要将一个 Employee 信息需要存储在变量引擎中:

    1. 为 Employee 创建一个继承 ASTNode 的实现 通过依赖注入的思想,将该实现注册到 VariableEngine中:
    // 1. 涉及 Employee 的 ASTNodeJSON 结构
    interface EmployeeASTJSON {
        employeeName: string;
    }
    
    // 2. 继承 ASTNode,细化 EmployeeASTNode 逻辑
    class EmployeeASTNode extends ASTNode<EmployeeASTJSON> {
        // 2.1. 为 EmployeeASTNode 设定一个 kind 用于标识其类别
        static kind: string = 'Employee';
    
        _employeeName: string;
    
        // 2.2. 实现 Employee 的 fromJSON,描述其更新逻辑
        fromJSON(json: EmployeeASTJSON) {
            if(json.employeeName !== this._employeeName) {
                this._employeeName = json.employeeName;
                this.fireChange();
            }
        }
        
        toJSON(): EmployeeASTJSON {
            return {
                kind: this.kind,
                employeeName: this._employeeName
            }
        }
    }
    
    // 3. 通过依赖注入,将 EmployeeASTNode 注册 到 variableEngine 中
    variableEngine.astRegisters.registerAST(EmployeeASTNode)
    

    2. 注册完成后, Scope 中即可以创建包含 Employee 的 AST 节点

    // 1. 创建 Employee 节点
    // 变量引擎会通过 ASTNodeJSON 中的 kind 找到 Employee 对应的实现
    const employeeNode = scope.ast.set('employee-test', {
        kind: 'Employee',
        employeeName: 'Mike Johnson'
    });
    
    // 2. 监听 Employee 信息的变化
    const disposable = employeeNode.subscribe(_node => {
        console.log(_node);
    })
    
    // 3. 更新 Employee 的名称
    employeeNode.fromJSON({
        employeeName: 'Blank Mikeson'
    })
    

    ScopeChain 是变量抽象层提供一个抽象类,包含三个抽象方法,用于让 Scope 获取依赖作用域和覆盖作用域:

    export abstract class ScopeChain {
      // ... 内置方法实现
    
      // 获取依赖作用域,子类实现
      abstract getDeps(scope: Scope): Scope[];
    
      // 获取覆盖作用域,子类实现
      abstract getCovers(scope: Scope): Scope[];
    
      // 获取所有作用域的排序
      abstract sortAll(): Scope[];
    }
    

    下面是一个模拟的简单作用域链实现:

    @injectable()
    export class MockScopeChain extends ScopeChain {
    
      // 所有作用域都依赖 global,global 不依赖任何作用域
      getDeps(scope: Scope): Scope[] {
        const res: Scope[] = [];
        if (scope.id === 'global') {
          return [];
        }
    
        const global = this.variableEngine.getScopeById('global');
        if (global) {
          res.push(global);
        }
    
        return res;
      }
    
      // global 可以覆盖所有作用域,但是其他作用域没有任何覆盖
      getCovers(scope: Scope): Scope[] {
        if (scope.id === 'global') {
          return this.variableEngine.getAllScopes().filter(_scope => _scope.id !== 'global');
        }
    
        return [];
      }
    
      sortAll(): Scope[] {
        return this.variableEngine.getAllScopes();
      }
    }
    
    // 作用域链实现会通过 inversify bind 依赖注入到变量引擎中
    new ContainerModule(bind => {
        bind(ScopeChain).to(MockScopeChain).inSingletonScope();
    })
    

    5.2. 变量实现层部分 API 简介

    类型 是一种 ASTNode 的实现

    import { BaseType } from "@flowgram.ai/variable-core";
    
    // 获取变量的类型
    const variable = scope.ast.get('variable-declaration');
    const vType: BaseType = variable.type;
    

    变量引擎内部提供了一套基于 JSON Types 的预设类型集合实现

    基础类型 复合类型
    基础类型提供了 Json 四种基本类型定义:String,Number,Integer,Boolean 复合类型可以下钻嵌套其他类型,也可以下钻嵌套其他声明:Object,Array,Map,Union
    const stringType = scope.ast.set('stringType', { kind: ASTKind.String })
    const numberType = scope.ast.set('numberType', { kind: ASTKind.Number })
    const integerType = scope.ast.set('integerType', { kind: ASTKind.Integer })
    const booleanType = scope.ast.set('booleanType', { kind: ASTKind.Boolean })
    
    const complicatedType = scope.ast.set('complicatedType', {
        kind: ASTKind.Object,
        properties: [
            // { a: string }
            {
                kind: ASTKind.Object,
                key: '_object',
                properties: [
                    { kind: ASTKind.Property, key: 'a', type: ASTKind.String }
                ]
            },
            // Map<String, String>
            {
                kind: ASTKind.Property,
                key: '_map_string',
                type: {
                    kind: ASTKind.Map,
                    valueType: { kind: ASTKind.String }
                }
            },
            // Array<{ test:string }>
            {
                kind: ASTKind.Property,
                key: '_array_object',
                type: {
                    kind: ASTKind.Array,
                    valueType: {
                        kind: ASTKind.Object,
                        properties: [{ kind: ASTKind.Property, key: 'test', type: ASTKind.String }]
                    }
                }
            }
            
        ]
    });
    
    // 获取 下钻 _object.a 字段的类型
    const objectDrilldownType = complicatedType.getByKeyPath(['_object', 'a']).type
    

    声明是一种 ASTNode 的实现,它提供了一系列方法,用于获取声明的信息及内容。变量引擎内置了 VariableDeclarationList、VariableDeclaration、Property 三种声明

    import { BaseVariableField } from "@flowgram.ai/variable-core";
    import { ASTKind, ObjectJSON, VariableDeclarationListJSON } from "@flowgram.ai/variable-core";
    
    const field: BaseVariableField<{
        label: string;
    }> = scope.ast.get('custom-variable');
    
    scope.ast.set('variable-list', {
      kind: ASTKind.VariableDeclarationList,
      declarations: [
        {
          type: ASTKind.String,
          key: 'string',
        },
        {
          // VariableDeclarationList 的 declarations 中可以不用声明 Kind
          // kind: ASTKind.VariableDeclaration,
          type: ASTKind.Number,
          key: 'number',
        },
        {
          kind: ASTKind.VariableDeclaration,
          type: ASTKind.Integer,
          key: 'integer',
        },
        {
          kind: ASTKind.VariableDeclaration,
          type: {
            kind: ASTKind.Object,
            properties: [
              {
                key: 'key1',
                type: ASTKind.String,
                // Object 的 properties 中可以不用声明 Kind
                kind: ASTKind.Property,
              },
              {
                key: 'key4',
                type: {
                  kind: ASTKind.Array,
                  items: {
                    kind: ASTKind.Object,
                    properties: [
                      {
                        key: 'key1',
                        type: ASTKind.Boolean,
                      },
                    ],
                  } as ObjectJSON,
                },
              },
            ],
          } as ObjectJSON,
          key: 'object',
        },
        {
          kind: ASTKind.VariableDeclaration,
          type: { kind: ASTKind.Map, valueType: ASTKind.Number },
          key: 'map',
        },
      ],
    });
    

    变量引擎可以通过 keyPath 访问到声明

    import { BaseVariableField } from "@flowgram.ai/variable-core";
    
    // 获取 key 为 string 的变量
    const stringVar: BaseVariableField = variableEngine.globalVariableTable.getByKeyPath(['string']);
    // 获取 key 为 boolean 的变量
    const booleanVar: BaseVariableField = variableEngine.globalVariableTable.getByKeyPath(['boolean']);
    
    // 获取 key 为 object 的变量,下钻到他内部的 key1 字段
    const objectKey1Var: BaseVariableField = variableEngine.globalVariableTable.getByKeyPath(['object', 'key1']);
    
    // 获取 key 为 object 的变量,下钻到他内部的 key2 字段,再下钻到 key2 字段中的 key1 字段
    const objectKey2Key1Var: BaseVariableField = variableEngine.globalVariableTable.getByKeyPath(['object', 'key2', 'key1']);
    
    // 获取 key 为 object 的变量,下钻到他内部的 key4 字段
    const objectKey4Var: BaseVariableField = variableEngine.globalVariableTable.getByKeyPath(['object', 'key4']);
    

    Scope.outputs 可以获取并监听 Scope 输出变量的信息:

    // 获取当前 Scope 输出的所有 VariableDeclaration
    console.log(scope.outputs.variables);
    
    // 获取当前 Scope 输出的所有 VariableDeclaration 的 key 值索引
    console.log(scope.outputs.variableKeys);
    
    // 在 Scope 的输出变量中寻找指定 key 的 VariableDeclaration
    scope.outputs.getVariableByKey('test')
    
    // 监听当前 Scope 输出的 VariableDeclaration 列表及下钻声明发生变化时
    scope.outputs.onListOrAnyVarChange(() => {
        console.log(scope.outputs.variables);
    });
    

    Scope.available 可以获取并监听 Scope 可访问变量的信息,即所有依赖作用域的输出变量:

    // 获取当前 Scope 依赖的所有可访问的 VariableDeclaration
    console.log(scope.available.variables)
    
    // 获取当前 Scope 输出的所有可访问的 key 值索引
    console.log(scope.available.variableKeys)
    
    // 在 Scope 可访问变量中寻找指定 key 的 VariableDeclaration
    scope.available.getVariableByKey('test')
    
    // 通过 keyPath 在 Scope 的可访问变量中找到变量或者下钻字段
    scope.available.getByKeyPath(['a', 'b', 'c'])
    
    // 监听当前 Scope 可访问的变量发生变化时
    scope.available.onListOrAnyVarChange(() => {
        console.log(scope.available.variables)
    });
    
    // 监听当前 Scope 可访问变量列表的变化
    scope.available.onVariableListChange(() => {
        console.log(scope.available.variables)
    });
    
    // 监听当前 Scope 可访问变量,任意一个变量内部的信息变化
    scope.available.onAnyVariableChange(() => {
        console.log(scope.available.variables)
    });
    

    系统预设提供了三个表达式的实现:

    • KeyPathExpression:通过 keyPath 引用单个变量
    • EnumerateExpression:对子表达式的返回类型进行遍历,获取遍历后的数据类型
    • WrapArrayExpression:将子表达式的返回类型封装成 Array

    声明抽象的 ASTNodeJSON 中,initializer 可以设置为一个表达式,该声明的类型会自动同步为该表达式的类型

    const variable = scope1.ast.set('variable', {
      kind: ASTKind.VariableDeclaration,
      key: 'a',
      type: {
        kind: ASTKind.Object,
        properties: [
          {
            key: 'b',
            type: {
              kind: ASTKind.Array,
              items: {
                kind: ASTKind.String,
              },
            },
          },
        ],
      },
    })
    
    // scope2 依赖 scope1
    const targetVariable = scope2.ast.set('variable2', {
        kind: ASTKind.VariableDeclaration,
        key: 'target',
        initializer: {
            // 对 子表达式的 Array 类型进行遍历,获取 items 的类型
            kind: ASTKind.EnumerateExpression,
            enumerateFor: {
                // 获取变量 a 下钻的 b 字段
                kind: ASTKind.KeyPathExpression
                keyPath: ['a', 'b']
            }
        }
    })
    
    // 变量的类型和变量 a 下钻 b 字段的数组类型的 Item 类型保持一致
    targetVariable.type.kind === ASTKind.String
    

    表达式联动变量类型 Demo 效果

❌
❌