十五万字长文 - 由浅入深设计一套低代码平台(4-2)

43 阅读4分钟

低代码平台架构设计方案 - 从浅入深完整指南(4-2)

前言: 本文系统阐述了一套完整的低代码平台架构设计方案,涵盖从基础架构到企业级应用的全链路技术实现。

核心内容:

🏗️ 四层架构体系:可视化设计器、Schema协议、运行时引擎、物料体系的完整设计

🔄 全局状态管理:基于Zustand的页面级Store架构,支持跨组件数据流动

⚡ 性能优化方案:三种发布模式(纯运行时、Schema编译、混合模式)对比与实践

🎯 动作系统:枚举化业务操作设计,实现配置化与安全性的平衡

🔧 Schema编译器:深度解析编译优化策略,在保持Runtime架构一致性的同时实现70%体积优化

🚀 SSR支持:Next.js集成方案,满足SEO与首屏性能需求

📦 发布流程:从Schema保存到产物部署的完整工程化实践

适合人群:前端架构师、低代码平台开发者、对前端工程化感兴趣的技术人员

全文15万+字,涵盖架构设计、核心实现、性能优化、工程实践等多个维度,提供可直接落地的技术方案。

5.7.1 全局方法 (Global Methods) 的实现原理

重要澄清: methods 不是字符串形式的函数体,而是 JSON 配置的动作序列

常见误解:

❌ 错误理解: methods 是字符串形式的代码
{
  "methods": {
    "handleDelete": "async function(userId) { await fetch(...) }"  // ❌ 错误!
  }
}

✅ 正确理解: methods 是动作序列的配置
{
  "methods": {
    "handleDelete": {
      "params": ["userId"],
      "actions": [                    // ← JSON 配置,不是代码字符串
        { "type": "confirm", ... },
        { "type": "request", ... }
      ]
    }
  }
}

实现原理:

┌─────────────────────────────────────────────────┐
│  用户配置全局方法 (Schema)                        │
├─────────────────────────────────────────────────┤
│  {                                              │
│    "methods": {                                 │
│      "handleDelete": {                          │
│        "params": ["userId"],                    │
│        "actions": [                             │
│          { "type": "confirm", ... },            │
│          { "type": "request", ... },            │
│          { "type": "message", ... }             │
│        ]                                        │
│      }                                          │
│    }                                            │
│  }                                              │
└────────────────┬────────────────────────────────┘
                 │ 运行时注册
                 ↓
┌─────────────────────────────────────────────────┐
│  Runtime 初始化时,将 methods 注册到 Map          │
├─────────────────────────────────────────────────┤
│  this.methods = new Map()                       │
│  this.methods.set('handleDelete', {             │
│    params: ['userId'],                          │
│    actions: [...]                               │
│  })                                             │
└────────────────┬────────────────────────────────┘
                 │ 组件事件触发
                 ↓
┌─────────────────────────────────────────────────┐
│  用户点击删除按钮                                 │
├─────────────────────────────────────────────────┤
│  <Button                                        │
│    onClick={{                                   │
│      type: "callMethod",                        │
│      payload: {                                 │
│        method: "handleDelete",                  │
│        params: [123]  ← 传入 userId             │
│      }                                          │
│    }}                                           │
│  />                                             │
└────────────────┬────────────────────────────────┘
                 │ 执行动作
                 ↓
┌─────────────────────────────────────────────────┐
│  运行时执行                                       │
├─────────────────────────────────────────────────┤
│  // callMethod 动作被触发                        │async callMethod(methodName, params) {         │
│    // 1. 从 Map 中获取方法配置                   │const methodConfig = this.methods.get(       │
│      'handleDelete'                             │
│    );                                           │
│                                                 │
│    // 2. 依次执行 actions 序列                   │for (const action of methodConfig.actions) { │
│      await actionRegistry.execute(              │
│        action.type,                             │
│        action.payload                           │
│      );                                         │
│    }                                            │
│  }                                              │
└─────────────────────────────────────────────────┘

完整示例:

{
  "store": {
    "users": []
  },

  "methods": {
    // ❌ 不是这样: "handleDelete": "function() { ... }"

    // ✅ 而是这样: JSON 配置的动作序列
    "handleDelete": {
      "params": ["userId"],  // 方法参数
      "actions": [           // 动作序列(按顺序执行)
        // 动作1: 弹确认框
        {
          "type": "confirm",
          "payload": { "message": "确认删除用户?" }
        },
        // 动作2: 发送删除请求
        {
          "type": "request",
          "payload": {
            "url": "/api/users/${params[0]}",  // 使用参数
            "method": "DELETE"
          }
        },
        // 动作3: 显示提示
        {
          "type": "message",
          "payload": {
            "type": "success",
            "content": "删除成功"
          }
        },
        // 动作4: 更新列表
        {
          "type": "setStore",
          "payload": {
            "users": "${store.users.filter(u => u.id !== params[0])}"
          }
        }
      ]
    }
  },

  "componentTree": {
    "children": [
      {
        "componentName": "Button",
        "props": { "children": "删除" },
        "events": {
          "onClick": {
            "type": "callMethod",      // 触发 callMethod 动作
            "payload": {
              "method": "handleDelete",  // 方法名
              "params": [123]            // 传入参数
            }
          }
        }
      }
    ]
  }
}

运行时实现:

class LowCodeRuntime {
  constructor(schema) {
    // 注册全局方法
    this.methods = new Map();
    if (schema.methods) {
      Object.entries(schema.methods).forEach(([name, config]) => {
        this.methods.set(name, config);
      });
    }
  }

  /**
   * 调用全局方法
   * @param {string} methodName - 方法名
   * @param {Array} params - 参数数组
   */
  async callMethod(methodName, params = []) {
    const methodConfig = this.methods.get(methodName);
    if (!methodConfig) {
      throw new Error(`Method "${methodName}" not found`);
    }

    console.log(`🎯 调用方法: ${methodName}`, params);

    // 构建上下文(包含参数)
    const context = {
      params,
      store: this.store.getStore(),
      dataSource: this.dataSource.getContext()
    };

    // 依次执行 actions 序列
    const results = [];
    for (const action of methodConfig.actions) {
      // 解析 payload 中的表达式(如 ${params[0]})
      const resolvedPayload = this.expression.evaluateObject(
        action.payload,
        context
      );

      // 执行动作
      const result = await this.actionRegistry.execute(
        action.type,
        resolvedPayload
      );

      results.push(result);

      // 如果是 confirm 返回 false,中断执行
      if (action.type === 'confirm' && !result) {
        console.log('❌ 用户取消操作');
        break;
      }
    }

    return results;
  }
}

关键点总结:

维度传统开发低代码平台
方法定义JavaScript 函数JSON 配置的动作序列
执行方式直接调用函数运行时解析并执行动作
代码形式function() { ... }{ params: [], actions: [...] }
灵活性高(可以写任何代码)中(受限于预定义动作)
安全性低(任意代码执行)高(只能使用注册的动作)
学习成本需要会编程配置即可,无需编程

为什么不用字符串形式的代码?

  1. 安全风险: eval()new Function() 可以执行任意代码,存在 XSS 风险
  2. 难以解析: 字符串代码难以静态分析和验证
  3. 不可移植: JavaScript 代码无法跨平台(如小程序)
  4. JSON 配置: 安全、可序列化、可验证、易于理解
  5. 动作系统: 提供足够的灵活性,同时保证安全

5.8 表达式引擎

支持的表达式类型:

// 1. 全局Store访问
"${store.userName}"
"${store.user.profile.email}"

// 2. 数据源访问
"${dataSource.userList.data}"
"${dataSource.userList.loading}"

// 3. 计算表达式
"${store.price * store.quantity}"
"${store.users.length > 0}"

// 4. 函数调用
"${store.users.filter(u => u.age > 18)}"
"${store.name.toUpperCase()}"

// 5. 三元表达式
"${store.isVip ? 'VIP用户' : '普通用户'}"

// 6. 模板字符串
"`用户: ${store.userName}, 年龄: ${store.age}`"

// 7. 混合使用
"${store.users.find(u => u.id === dataSource.currentUserId.data)}"

表达式引擎实现:

// runtime/ExpressionEngine.js
class ExpressionEngine {
  constructor(runtime) {
    this.runtime = runtime;
    this.cache = new Map(); // 表达式缓存
  }

  /**
   * 评估表达式
   *
   * 表达式上下文说明:
   * - store: 全局 Store (来自 schema.store)
   * - dataSource: 数据源状态 (来自 runtime.dataSource)
   * - 不提供 state,避免与 store 混淆
   */
  evaluate(expression, extraContext = {}) {
    if (typeof expression !== 'string' || !expression.includes('${')) {
      return expression;
    }

    // 检查缓存
    let compiledFn = this.cache.get(expression);
    if (!compiledFn) {
      compiledFn = this.compile(expression);
      this.cache.set(expression, compiledFn);
    }

    // 构建表达式上下文
    const context = {
      store: this.runtime.store.getStore(),        // 全局 Store
      dataSource: this.getDataSourceContext(),     // 数据源
      ...extraContext                              // 额外的上下文(如事件参数)
    };

    try {
      return compiledFn(context);
    } catch (error) {
      console.error('Expression evaluation error:', error);
      console.error('Expression:', expression);
      console.error('Context:', context);
      return undefined;
    }
  }

  // 编译表达式为函数
  compile(expression) {
    const code = expression.slice(2, -1); // 去除 ${ }

    // 使用 with 语句创建上下文作用域
    // 允许直接访问 store.xxx 而不是 context.store.xxx
    return new Function('context', `
      with(context) {
        return ${code};
      }
    `);
  }

  // 获取数据源上下文
  getDataSourceContext() {
    const context = {};
    this.runtime.dataSource.dataSources.forEach((ds, id) => {
      context[id] = {
        data: ds.data,
        loading: ds.loading,
        error: ds.error
      };
    });
    return context;
  }

  /**
   * 批量解析对象中的所有表达式
   * 用于解析组件的 props
   */
  evaluateObject(obj, extraContext = {}) {
    if (!obj || typeof obj !== 'object') {
      return obj;
    }

    const result = Array.isArray(obj) ? [] : {};

    for (const [key, value] of Object.entries(obj)) {
      if (typeof value === 'string') {
        result[key] = this.evaluate(value, extraContext);
      } else if (typeof value === 'object' && value !== null) {
        result[key] = this.evaluateObject(value, extraContext);
      } else {
        result[key] = value;
      }
    }

    return result;
  }
}

/**
 * 使用示例:
 *
 * const engine = new ExpressionEngine(runtime);
 *
 * // 简单访问
 * engine.evaluate("${store.userName}");  // → "John"
 *
 * // 计算
 * engine.evaluate("${store.users.length}");  // → 5
 *
 * // 复杂表达式
 * engine.evaluate("${store.users.filter(u => u.active).map(u => u.name)}");
 * // → ["Alice", "Bob"]
 *
 * // 批量解析
 * engine.evaluateObject({
 *   users: "${store.users}",
 *   count: "${store.users.length}",
 *   title: "用户列表"
 * });
 * // → { users: [...], count: 5, title: "用户列表" }
 */

5.9 完整运行时实现

// runtime/LowCodeRuntime.js
/**
 * 低代码运行时 - 整个页面的核心引擎
 *
 * 架构说明:
 * - 每个页面有一个 Runtime 实例 (页面级单例)
 * - Runtime 包含全局 Store、全局 Methods、事件系统等
 * - 所有组件通过 Runtime 访问数据和触发动作
 */
class LowCodeRuntime {
  constructor(schema) {
    this.schema = schema;

    console.log('🚀 初始化 LowCodeRuntime');

    // 初始化全局状态管理器
    this.store = new GlobalStoreManager(schema.store || {});
    console.log('✅ GlobalStore 初始化完成:', schema.store);

    // 初始化事件总线 (用于兄弟组件通信)
    this.eventBus = new EventBus();

    // 初始化表达式引擎 (解析 ${store.xxx})
    this.expression = new ExpressionEngine(this);

    // 初始化动作注册表 (内置动作 + 自定义动作)
    this.actionRegistry = new ActionRegistry(this);

    // 初始化事件系统 (处理组件事件)
    this.eventSystem = new EventSystem(this);

    // 初始化数据源管理器
    this.dataSource = new DataSourceManager(this);

    // 注册全局方法
    this.methods = new Map();
    if (schema.methods) {
      Object.entries(schema.methods).forEach(([name, config]) => {
        this.methods.set(name, config);
        console.log(`✅ 注册全局方法: ${name}`);
      });
    }

    // 异步初始化
    this.init();
  }

  /**
   * 初始化运行时
   */
  async init() {
    try {
      // 注册数据源
      if (this.schema.dataSources) {
        this.schema.dataSources.forEach(ds => {
          this.dataSource.register(ds);
          console.log(`✅ 注册数据源: ${ds.id}`);
        });
      }

      // 执行 onMount 生命周期
      if (this.schema.lifeCycles?.onMount) {
        console.log('🔄 执行 onMount 生命周期');
        await this.eventSystem.executeActionSequence(
          this.schema.lifeCycles.onMount,
          {}
        );
      }

      console.log('✅ Runtime 初始化完成');
    } catch (error) {
      console.error('❌ Runtime 初始化失败:', error);
    }
  }

  /**
   * 调用全局方法
   */
  async callMethod(methodName, params = []) {
    const methodConfig = this.methods.get(methodName);
    if (!methodConfig) {
      throw new Error(`Method "${methodName}" not found`);
    }

    console.log(`🎯 调用全局方法: ${methodName}`, params);

    // 执行方法的 actions 序列
    return await this.eventSystem.executeActionSequence(
      methodConfig.actions,
      { params }
    );
  }

  /**
   * 销毁运行时
   */
  destroy() {
    try {
      // 执行 onUnmount 生命周期
      if (this.schema.lifeCycles?.onUnmount) {
        console.log('🔄 执行 onUnmount 生命周期');
        this.eventSystem.executeActionSequence(
          this.schema.lifeCycles.onUnmount,
          {}
        );
      }

      // 清理事件总线
      this.eventBus.clear();

      console.log('✅ Runtime 销毁完成');
    } catch (error) {
      console.error('❌ Runtime 销毁失败:', error);
    }
  }
}

/**
 * 使用示例:
 *
 * const schema = {
 *   store: { users: [], loading: false },
 *   methods: { handleDelete: { actions: [...] } },
 *   dataSources: [{ id: 'userApi', ... }],
 *   componentTree: { ... }
 * };
 *
 * const runtime = new LowCodeRuntime(schema);
 *
 * // 访问全局 Store
 * const users = runtime.store.getStore('users');
 * runtime.store.setStore({ loading: true });
 *
 * // 调用全局方法
 * await runtime.callMethod('handleDelete', [userId]);
 *
 * // 解析表达式
 * const value = runtime.expression.evaluate('${store.users.length}');
 */