ArkUI Syntax 模块架构总览

文档版本:v1.0 更新时间:2026-02-05 源码版本:OpenHarmony ace_engine (master 分支) 作者:Claude Code


目录


模块概述

定位与职责

Syntax 模块是 ArkUI 框架中负责语法结构和控制流的核心模块,提供声明式 UI 中的循环渲染、条件分支、作用域管理等关键能力。

核心职责

  1. 循环渲染:提供多种循环策略,支持标准循环、懒加载、虚拟滚动
  2. 条件渲染:实现 If/Else 分支逻辑,动态切换 UI 内容
  3. 作用域管理:管理主题作用域、内容插槽等特殊作用域
  4. 数据驱动:响应数据变化,自动更新 UI 结构

模块位置

ace_engine/
└── frameworks/core/components_ng/pattern/syntax/
    ├── 循环组件(70%)       - ForEach, LazyForEach, Repeat, VirtualScroll(独立组件)
    ├── 条件渲染(10%)       - IfElse
    ├── 特殊语法(10%)       - WithTheme, ContentSlot
    ├── Arkoala支持(5%)     - Arkoala 语言适配
    └── 工具类(5%)          - NodeContent, Builder, Cache

核心特性

1. 多样化循环策略

  • ForEach:标准循环,适合小规模数据(< 100 项)
  • LazyForEach:懒加载循环,按需创建/销毁节点,适合大规模数据
  • Repeat:重复渲染,固定次数循环
  • RepeatVirtualScroll:虚拟滚动,高性能长列表渲染

2. 高性能条件渲染

  • 基于分支 ID(branchId_)的快速切换
  • 支持分支复用(TryRetake
  • 最小化 DOM 操作

3. 作用域隔离

  • 主题作用域(WithTheme)隔离样式上下文
  • 内容插槽(ContentSlot)管理动态内容

4. 多前端支持

  • 声明式前端(ArkTS/TypeScript)
  • CJ 前端(Cangjie 语言)
  • Arkoala 语言支持

组件分类体系

按功能分类

类别 组件 核心能力 典型场景
标准循环 ForEachNode 完整数据绑定,自动 diff 小列表(< 100 项)
懒加载循环 LazyForEachNode 按需创建/销毁,支持缓存 长列表(1000+ 项)
固定循环 RepeatNode 固定次数重复 分页器、星级评分
虚拟滚动 RepeatVirtualScroll2Node 视口内渲染,缓存复用 超长列表(10000+ 项)
条件渲染 IfElseNode 分支切换,复用机制 Tab 切换、状态展示
主题作用域 WithThemeNode 主题隔离,自动销毁回调 主题切换、局部样式
内容插槽 ContentSlotNode 动态内容管理 模板组件、对话框

按性能特性分类

性能等级 组件 内存占用 初始化速度 更新性能 适用规模
轻量级 ForEach, Repeat, IfElse 低(全加载) 中(全量 diff) < 100 项
中量级 LazyForEach 中(视口 + 缓存) 快(增量更新) 100 - 1000 项
重量级 RepeatVirtualScroll 低(仅视口) 慢(缓存初始化) 最快(虚拟滚动) 1000+ 项

按数据驱动方式分类

驱动方式 组件 数据源 更新机制 Diff 算法
全量响应 ForEach Iterable 数据监听 Key-based diff
懒加载响应 LazyForEach IDataSource 按需请求 Key-based diff
固定次数 Repeat 数字 无数据绑定 无 diff
条件驱动 IfElse 布尔/枚举 条件变化 分支 ID 切换

核心基类详解

继承体系总览

UINode (components_ng/base/ui_node.h)
    │
    ├── SyntaxItem ──────────────────────────────► 所有语法节点的基类
    │       │
    │       ├── ForEachBaseNode ────────────────► 循环组件基类(模板方法模式)
    │       │       │
    │       │       ├── ForEachNode
    │       │       ├── RepeatNode
    │       │       ├── LazyForEachNode
    │       │       └── RepeatVirtualScroll2Node
    │       │
    │       ├── IfElseNode ─────────────────────► 条件分支渲染
    │       ├── WithThemeNode ──────────────────► 主题作用域
    │       └── ContentSlotNode ────────────────► 内容插槽
    │
    └── ArkoalaSyntaxItem ──────────────────────► Arkoala 语言专用基类
            │
            ├── ArkoalaForEachNode
            ├── ArkoalaLazyNode
            └── ArkoalaConditionScope

SyntaxItem(语法节点基类)

源码位置frameworks/core/components_ng/pattern/syntax/syntax_item.h

核心职责

  • 为所有语法节点提供公共接口
  • 管理语法节点的生命周期
  • 提供语法节点的统一标识

关键接口

class SyntaxItem : public UINode {
public:
    // 语法节点标识
    virtual bool IsSyntaxNode() const override { return true; }

    // 获取语法节点类型
    virtual SyntaxItemType GetSyntaxItemType() const = 0;

protected:
    SyntaxItem(int32_t nodeId);
};

设计意图

  • 统一抽象:所有语法节点共享基类,便于统一管理
  • 类型识别:通过 IsSyntaxNode() 快速识别语法节点
  • 扩展性:新增语法组件只需继承 SyntaxItem

ForEachBaseNode(循环组件基类)

源码位置frameworks/core/components_ng/pattern/syntax/for_each_base_node.h

核心职责

  • 定义循环组件的公共行为
  • 提供子节点管理的通用实现
  • 实现模板方法模式

关键接口

class ForEachBaseNode : public SyntaxItem {
public:
    // 获取当前子节点数量
    virtual int32_t GetChildCount() const = 0;

    // 根据索引获取子节点
    virtual RefPtr<UINode> GetChildByIndex(int32_t index) const = 0;

    // 刷新所有子节点
    virtual void RefreshAll() = 0;

    // 清理所有子节点
    virtual void ClearAll() = 0;

protected:
    // 子节点列表管理
    std::vector<RefPtr<UINode>> children_;
};

设计模式:模板方法模式

ForEachBaseNode(抽象类)
    │
    ├── 定义公共接口模板:GetChildCount(), GetChildByIndex()
    ├── 定义算法骨架:RefreshAll()
    │
    └── 子类实现具体细节:
        ├── ForEachNode:基于 Iterable 的全量刷新
        ├── LazyForEachNode:基于 DataSource 的按需刷新
        ├── RepeatNode:固定次数的简单重复
        └── RepeatVirtualScroll2Node:虚拟滚动的缓存刷新

优势

  • 代码复用:子节点管理逻辑统一实现
  • 统一接口:所有循环组件对外接口一致
  • 扩展性强:新增循环组件只需实现特定逻辑

NodeContent(节点内容管理器)

源码位置frameworks/core/components_ng/pattern/syntax/node_content.h

核心职责

  • 封装单个节点的内容
  • 管理节点的生命周期
  • 提供节点内容的安全访问

关键接口

class NodeContent {
public:
    // 获取内容节点
    RefPtr<UINode> GetNode() const { return contentNode_; }

    // 设置内容节点
    void SetNode(const RefPtr<UINode>& node) { contentNode_ = node; }

    // 销毁内容节点
    void Destroy();

private:
    RefPtr<UINode> contentNode_;
};

使用场景

  • LazyForEach:管理按需创建的子节点内容
  • ContentSlot:管理插槽内的动态内容
  • IfElse:管理分支节点的内容复用

循环组件详解

重要说明: ForEach、LazyForEach、Repeat、RepeatVirtualScroll 是四个完全独立的组件,它们继承自同一个基类(ForEachBaseNode)但各自有不同的实现和用途,不存在相互依赖或包含关系。

组件对比总览

特性 ForEach LazyForEach Repeat RepeatVirtualScroll
数据源类型 Iterable IDataSource number IDataSource
节点创建时机 初始化时全量创建 按需创建(懒加载) 初始化时全量创建 视口内按需创建
Diff 算法 Key-based diff Key-based diff 无 diff 虚拟滚动 diff
缓存机制 支持 onCached 支持多级缓存
适用数据规模 < 100 项 100 - 1000 项 固定次数(< 10) 1000+ 项
内存占用 高(全加载) 中(视口 + 缓存) 低(固定次数) 低(仅视口)
滚动性能 最快 最快
复杂度

ForEachNode(标准循环)

源码位置

  • frameworks/core/components_ng/pattern/syntax/for_each_node.h
  • frameworks/core/components_ng/pattern/syntax/for_each_node.cpp

核心特性

  • 全量加载:初始化时为所有数据项创建节点
  • 自动 Diff:基于 Key 的高效更新算法
  • 简单易用:API 最简单,适合快速开发

典型使用场景

// 小规模列表(如设置页、菜单)
ForEach(['Apple', 'Banana', 'Orange'], (item) => {
  Text(item)
})

性能特征

  • ✅ 初始化速度快(数据量小时)
  • ✅ 更新逻辑简单(全量 diff)
  • ❌ 内存占用高(全量加载)
  • ❌ 不适合长列表(> 100 项)

关键方法

class ForEachNode : public ForEachBaseNode {
public:
    // 刷新所有子节点(全量 diff)
    void RefreshAll() override;

    // 根据 ID 获取对应的子节点
    RefPtr<UINode> GetChildByIndex(int32_t index) const override;

private:
    // 当前渲染的子节点列表
    std::vector<RefPtr<UINode>> children_;

    // 数据项到节点的映射(用于 diff)
    std::map<std::string, RefPtr<UINode>> itemNodeMap_;
};

源码文件规模

  • for_each_node.h:约 150 行
  • for_each_node.cpp:约 400 行

LazyForEachNode(懒加载循环)

源码位置

  • frameworks/core/components_ng/pattern/syntax/lazy_for_each_node.h
  • frameworks/core/components_ng/pattern/syntax/lazy_for_each_node.cpp

核心特性

  • 按需加载:仅为视口内及附近的数据项创建节点
  • 缓存机制:支持 onCached 配置,控制缓存数量
  • DataSource 接口:实现 IDataSource 提供数据

典型使用场景

// 长列表(如新闻列表、商品列表)
LazyForEach(dataSource, (item) => {
  ListItem({ content: Text(item.name) })
})

DataSource 接口

class IDataSource {
public:
    // 获取数据总数
    virtual int TotalCount() = 0;

    // 获取指定索引的数据
    virtual T GetData(int index) = 0;

    // 注册数据变化监听器
    virtual void RegisterDataChangeListener(DataChangeListener listener) = 0;

    // 注销数据变化监听器
    virtual void UnregisterDataChangeListener(DataChangeListener listener) = 0;
};

性能优化

  • ✅ 内存占用可控(仅加载视口 + 缓存)
  • ✅ 滚动性能优秀(按需创建/销毁)
  • ✅ 支持大数据集(1000+ 项)
  • ⚠️ 需要实现 IDataSource 接口
  • ⚠️ 初始化速度中等(需要配置缓存)

关键方法

class LazyForEachNode : public ForEachBaseNode {
public:
    // 刷新所有子节点(增量更新)
    void RefreshAll() override;

    // 根据索引获取子节点(可能触发懒加载)
    RefPtr<UINode> GetChildByIndex(int32_t index) const override;

    // 配置缓存参数
    void SetCachedCount(int cachedCount);

private:
    // 懒加载构建器
    RefPtr<LazyForEachBuilder> builder_;

    // 缓存配置
    int cachedCount_ = 0;  // 缓存项数量
};

源码文件规模

  • lazy_for_each_node.h:约 200 行
  • lazy_for_each_node.cpp:约 600 行

RepeatNode(固定循环)

源码位置

  • frameworks/core/components_ng/pattern/syntax/repeat_node.h
  • frameworks/core/components_ng/pattern/syntax/repeat_node.cpp

核心特性

  • 固定次数:基于数字的简单重复
  • 无数据绑定:不依赖外部数据源
  • 极简实现:代码量最小

典型使用场景

// 分页器、星级评分
Repeat(5, () => {
  Dot().active(index === current)
})

性能特征

  • ✅ 实现简单(无复杂逻辑)
  • ✅ 性能优秀(固定次数,无 diff)
  • ❌ 灵活性低(仅固定次数)
  • ❌ 无数据驱动(不支持动态更新)

关键方法

class RepeatNode : public ForEachBaseNode {
public:
    // 初始化指定次数的重复
    void InitRepeat(int32_t count);

    // 刷新所有子节点(全量重建)
    void RefreshAll() override;

private:
    // 重复次数
    int32_t repeatCount_;

    // 生成子项的闭包
    std::function<RefPtr<UINode>()> itemGenerator_;
};

源码文件规模

  • repeat_node.h:约 80 行
  • repeat_node.cpp:约 150 行

RepeatVirtualScroll2Node(虚拟滚动)

源码位置

  • frameworks/core/components_ng/pattern/syntax/repeat_virtual_scroll_2_node.h
  • frameworks/core/components_ng/pattern/syntax/repeat_virtual_scroll_2_node.cpp

核心特性

  • 虚拟滚动:仅渲染视口内的节点
  • 多级缓存:支持上下预加载缓存
  • 高性能:适合超长列表(10000+ 项)

典型使用场景

// 超长列表(如社交媒体 Feed、无限滚动)
RepeatVirtualScroll(dataSource, (item) => {
  FeedItem({ content: Text(item.text) })
})

缓存系统

class RepeatVirtualScrollCaches {
public:
    // 获取视口上方的缓存
    std::vector<RefPtr<UINode>>& GetUpperCaches();

    // 获取视口下方的缓存
    std::vector<RefPtr<UINode>>& GetLowerCaches();

    // 获取视口内的节点
    std::vector<RefPtr<UINode>>& GetViewportNodes();

private:
    // 缓存配置
    int upperCacheCount_ = 5;   // 上方缓存数量
    int lowerCacheCount_ = 5;   // 下方缓存数量
};

性能优化

  • ✅ 内存占用最低(仅视口 + 少量缓存)
  • ✅ 滚动性能最佳(节点复用)
  • ✅ 支持超大规模(10000+ 项)
  • ⚠️ 实现复杂度高
  • ⚠️ 初始化速度慢(需要初始化缓存系统)

关键方法

class RepeatVirtualScroll2Node : public ForEachBaseNode {
public:
    // 根据滚动位置更新视口节点
    void UpdateViewportNodes(float scrollOffset);

    // 刷新所有子节点(虚拟滚动 diff)
    void RefreshAll() override;

private:
    // 虚拟滚动缓存系统
    RefPtr<RepeatVirtualScrollCaches> caches_;

    // 视口信息
    float viewportStart_;
    float viewportEnd_;
};

源码文件规模

  • repeat_virtual_scroll_2_node.h:约 250 行
  • repeat_virtual_scroll_2_node.cpp:约 800 行

条件渲染组件

IfElseNode(条件分支渲染)

源码位置

  • frameworks/core/components_ng/pattern/syntax/if_else_node.h
  • frameworks/core/components_ng/pattern/syntax/if_else_node.cpp

核心特性

  • 分支切换:基于条件动态切换显示内容
  • 分支复用:支持 TryRetake() 机制,复用已创建的分支节点
  • 高性能:仅渲染当前激活的分支

典型使用场景

// Tab 切换、状态展示
if (condition) {
  ComponentA()
} else {
  ComponentB()
}

核心机制:分支 ID 管理

class IfElseNode : public SyntaxItem {
public:
    // 设置当前激活的分支 ID
    void SetBranchId(int32_t branchId);

    // 尝试复用指定分支的节点
    RefPtr<UINode> TryRetake(int32_t branchId);

    // 收集所有可复用的分支节点
    std::vector<RefPtr<UINode>> CollectRetakenNodes();

    // 根据分支 ID 获取对应的子节点
    RefPtr<UINode> GetChildByBranchId(int32_t branchId) const;

private:
    // 当前激活的分支 ID(0 = if 分支,1 = else 分支)
    int32_t branchId_ = 0;

    // 分支节点映射表
    std::map<int32_t, RefPtr<UINode>> branchNodes_;
};

分支切换流程

用户触发条件变化
    ↓
调用 SetBranchId(newBranchId)
    ↓
检查 newBranchId 对应的节点是否存在
    ↓
    ├─ 存在 → 直接切换显示,隐藏旧分支
    └─ 不存在 → 创建新分支节点,隐藏旧分支
        ↓
    可选:调用 TryRetake() 复用旧分支节点(优化性能)

性能优化

  • ✅ 仅渲染激活分支(节省内存)
  • ✅ 分支复用机制(减少创建开销)
  • ✅ 切换速度快(无需全量刷新)
  • ⚠️ 分支状态不保留(切换后重新创建)

源码文件规模

  • if_else_node.h:约 120 行
  • if_else_node.cpp:约 300 行

特殊语法组件

WithThemeNode(主题作用域)

源码位置

  • frameworks/core/components_ng/pattern/syntax/with_theme_node.h
  • frameworks/core/components_ng/pattern/syntax/with_theme_node.cpp

核心特性

  • 作用域隔离:为子树提供独立的主题上下文
  • 自动销毁回调:作用域销毁时触发清理逻辑
  • 主题继承:支持继承父级主题并覆盖部分属性

典型使用场景

// 局部主题切换
WithTheme({ colors: { primary: 'red' } }, () => {
  Button()  // 使用红色主题
})

关键接口

class WithThemeNode : public SyntaxItem {
public:
    // 设置主题作用域销毁回调
    void SetThemeScopeDestroyCallback(ThemeScopeDestroyCallback callback);

    // 获取当前主题配置
    const ThemeConfig& GetThemeConfig() const;

    // 设置主题配置
    void SetThemeConfig(const ThemeConfig& config);

private:
    // 主题配置
    ThemeConfig themeConfig_;

    // 作用域销毁回调
    ThemeScopeDestroyCallback destroyCallback_;
};

设计模式:作用域模式

全局主题(默认)
    │
    ├─ WithTheme(作用域 A,覆盖 primary)
    │   │
    │   └─ Button(使用作用域 A 的 primary)
    │
    └─ WithTheme(作用域 B,覆盖所有颜色)
        │
        └─ Button(使用作用域 B 的所有颜色)

源码文件规模

  • with_theme_node.h:约 100 行
  • with_theme_node.cpp:约 200 行

ContentSlotNode(内容插槽)

源码位置

  • frameworks/core/components_ng/pattern/syntax/content_slot_node.h
  • frameworks/core/components_ng/pattern/syntax/content_slot_node.cpp

核心特性

  • 动态内容管理:支持动态挂载/卸载内容节点
  • 插槽复用:多个插槽可共享同一个内容节点
  • 生命周期管理:自动管理内容节点的创建和销毁

典型使用场景

// 模板组件、对话框
CustomDialog({
  content: ContentSlot(),
  onConfirm: () => {}
})

关键接口

class ContentSlotNode : public SyntaxItem {
public:
    // 挂载内容节点到插槽
    void AttachNodeContent(const RefPtr<NodeContent>& content);

    // 从插槽卸载内容节点
    void DetachNodeContent();

    // 获取当前挂载的内容节点
    RefPtr<NodeContent> GetNodeContent() const;

    // 检查插槽是否为空
    bool IsEmpty() const;

private:
    // 当前挂载的内容
    RefPtr<NodeContent> nodeContent_;
};

使用流程

创建 ContentSlotNode
    ↓
    ├─ 场景 1:挂载内容
    │   调用 AttachNodeContent(content)
    │       ↓
    │   将 content 渲染到插槽位置
    │
    └─ 场景 2:卸载内容
        调用 DetachNodeContent()
            ↓
        从插槽移除 content,销毁节点

源码文件规模

  • content_slot_node.h:约 90 行
  • content_slot_node.cpp:约 180 行

前端桥接层集成

架构总览

前端层(ArkTS/Cangjie)
    │
    ↓ Bridge 调用
┌─────────────────────────────────────────┐
│  前端桥接层(frameworks/bridge/)       │
├─────────────────────────────────────────┤
│  声明式前端(declarative_frontend/)    │
│  ├── jsview/js_for_each.cpp             │
│  ├── jsview/js_lazy_foreach.cpp         │
│  ├── jsview/js_repeat.cpp               │
│  ├── jsview/js_repeat_virtual_scroll.cpp│
│  └── jsview/js_if_else.cpp              │
│                                         │
│  CJ 前端(cj_frontend/)                │
│  ├── cppview/lazy_foreach.cpp           │
│  ├── cppview/if_else.cpp                │
│  └── interfaces/cj_ffi/cj_lazy_foreach_ffi.cpp │
└─────────────────────────────────────────┘
    │
    ↓ 创建节点
┌─────────────────────────────────────────┐
│  Pattern 层(components_ng/pattern/)   │
├─────────────────────────────────────────┤
│  ForEachNode, LazyForEachNode, ...      │
└─────────────────────────────────────────┘

声明式前端桥接

ForEach 桥接

源码位置frameworks/bridge/declarative_frontend/jsview/js_for_each.cpp

核心职责

  • 解析 ArkTS 中的 ForEach 语法
  • 创建 ForEachNode 实例
  • 配置数据源和生成器函数

关键流程

// js_for_each.cpp
void ForEachNode::Create(const std::vector<std::string>& keys,
                         const JSRef<JSFunc>& itemGen,
                         const JSRef<JSFunc>& keyGen)
{
    // 1. 创建 ForEachNode 实例
    auto forEachNode = AceType::MakeRefPtr<ForEachNode>();

    // 2. 配置数据源
    forEachNode->SetDataSource(keys);

    // 3. 配置子项生成器
    forEachNode->SetItemGenerator([itemGen](const std::string& item) {
        return itemGen->Call(item);  // 调用 ArkTS 生成器函数
    });

    // 4. 挂载到组件树
    GetParentNode()->AddChild(forEachNode);
}

ArkTS 使用示例

// ArkTS 代码
ForEach(['Apple', 'Banana', 'Orange'], (item: string) => {
  Text(item)
}, (item: string) => item.toString())  // Key 生成器

LazyForEach 桥接

源码位置frameworks/bridge/declarative_frontend/jsview/js_lazy_foreach.cpp

核心职责

  • 解析 ArkTS 中的 LazyForEach 语法
  • 创建 LazyForEachNode 实例
  • 配置 IDataSource 和缓存参数

关键流程

// js_lazy_foreach.cpp
void LazyForEachNode::Create(const JSRef<JSObject>& dataSource,
                             const JSRef<JSFunc>& itemGen,
                             const LazyForEachConfig& config)
{
    // 1. 创建 LazyForEachNode 实例
    auto lazyForEachNode = AceType::MakeRefPtr<LazyForEachNode>();

    // 2. 包装 JS DataSource 为 C++ IDataSource
    auto cppDataSource = std::make_shared<JSDataSource>(dataSource);
    lazyForEachNode->SetDataSource(cppDataSource);

    // 3. 配置缓存参数
    lazyForEachNode->SetCachedCount(config.cachedCount);

    // 4. 配置子项生成器
    lazyForEachNode->SetItemGenerator([itemGen](const T& item) {
        return itemGen->Call(item);
    });

    // 5. 挂载到组件树
    GetParentNode()->AddChild(lazyForEachNode);
}

ArkTS 使用示例

// ArkTS 代码
class MyDataSource implements IDataSource {
  data: string[] = ['Apple', 'Banana', 'Orange'];
  totalCount(): number { return this.data.length; }
  getData(index: number): string { return this.data[index]; }
}

LazyForEach(new MyDataSource(), (item: string) => {
  ListItem({ content: Text(item) })
})

IfElse 桥接

源码位置frameworks/bridge/declarative_frontend/jsview/js_if_else.cpp

核心职责

  • 解析 ArkTS 中的 if/else 语法
  • 创建 IfElseNode 实例
  • 根据条件动态切换分支

关键流程

// js_if_else.cpp
void IfElseNode::UpdateBranchId(const JSRef<JSVal>& condition)
{
    // 1. 评估条件表达式
    bool isTrue = condition->ToBoolean();

    // 2. 计算分支 ID
    int32_t newBranchId = isTrue ? 0 : 1;

    // 3. 更新 IfElseNode 的分支 ID
    ifElseNode_->SetBranchId(newBranchId);

    // 4. 触发重新渲染
    ifElseNode_->MarkDirty();
}

ArkTS 使用示例

// ArkTS 代码
if (this.isLoggedIn) {
  UserProfile()
} else {
  LoginButton()
}

CJ 前端桥接

LazyForEach 桥接(Cangjie)

源码位置

  • frameworks/bridge/cj_frontend/cppview/lazy_foreach.cpp
  • frameworks/bridge/cj_frontend/interfaces/cj_ffi/cj_lazy_foreach_ffi.cpp

核心职责

  • 解析 Cangjie 语言的 LazyForEach 语法
  • 通过 FFI 接口调用 C++ 实现
  • 配置 IDataSource 和缓存参数

关键流程

// cj_lazy_foreach_ffi.cpp
extern "C" void LazyForEach_Create(
    int64_t parentNodeId,
    int64_t dataSourcePtr,
    int32_t cachedCount,
    int64_t itemGenFuncPtr
)
{
    // 1. 获取父节点
    auto parentNode = GetFrameNodeById(parentNodeId);

    // 2. 创建 LazyForEachNode
    auto lazyForEachNode = AceType::MakeRefPtr<LazyForEachNode>();

    // 3. 配置 DataSource(从 Cangjie 传入)
    auto dataSource = reinterpret_cast<IDataSource*>(dataSourcePtr);
    lazyForEachNode->SetDataSource(dataSource);

    // 4. 配置缓存参数
    lazyForEachNode->SetCachedCount(cachedCount);

    // 5. 配置子项生成器(Cangjie 函数指针)
    lazyForEachNode->SetItemGenerator([itemGenFuncPtr](const T& item) {
        return CallCangjieFunction(itemGenFuncPtr, item);
    });

    // 6. 挂载到组件树
    parentNode->AddChild(lazyForEachNode);
}

Cangjie 使用示例

// Cangjie 代码
class MyDataSource {
    func totalCount(): Int64 { return 100 }
    func getData(index: Int64): String { return "Item ${index}" }
}

LazyForEach(MyDataSource(), cachedCount: 10) { item =>
    Text(item)
}

前端对比

特性 声明式前端(ArkTS) CJ 前端(Cangjie)
语言 ArkTS/TypeScript Cangjie
运行时 V8 / arkjs arkjs (Panda)
数据源接口 JavaScript 对象 C++ IDataSource
生成器函数 JSFunction 函数指针 + FFI
类型安全 弱类型 强类型
性能 中(桥接开销) 高(直接调用)

设计模式总结

1. 模板方法模式(ForEachBaseNode)

定义:在基类中定义算法骨架,将某些步骤延迟到子类实现。

应用场景:循环组件的刷新逻辑

ForEachBaseNode::RefreshAll()
    │
    ├─ 步骤 1:清理旧节点(基类实现)
    ├─ 步骤 2:计算新节点列表(子类实现)
    │   ├─ ForEachNode:全量遍历 Iterable
    │   ├─ LazyForEachNode:按需请求 DataSource
    │   └─ RepeatNode:固定次数重复
    └─ 步骤 3:Diff 算法(子类实现)
        ├─ ForEachNode:Key-based diff
        ├─ LazyForEachNode:Key-based diff
        └─ RepeatNode:无 diff,全量重建

优势

  • 代码复用(公共逻辑在基类)
  • 统一接口(所有循环组件行为一致)
  • 扩展性强(新增循环组件只需实现特定步骤)

示例代码

// 基类定义算法骨架
class ForEachBaseNode {
public:
    void RefreshAll() {
        // 步骤 1:清理旧节点
        ClearOldNodes();

        // 步骤 2:计算新节点列表(子类实现)
        auto newNodes = CalculateNewNodes();

        // 步骤 3:Diff 算法(子类实现)
        DiffAndUpdate(newNodes);
    }

protected:
    virtual std::vector<RefPtr<UINode>> CalculateNewNodes() = 0;
    virtual void DiffAndUpdate(const std::vector<RefPtr<UINode>>& newNodes) = 0;
};

// 子类实现具体步骤
class ForEachNode : public ForEachBaseNode {
protected:
    std::vector<RefPtr<UINode>> CalculateNewNodes() override {
        // 全量遍历 Iterable,创建所有节点
        std::vector<RefPtr<UINode>> nodes;
        for (const auto& item : iterable_) {
            nodes.push_back(CreateNodeFromItem(item));
        }
        return nodes;
    }

    void DiffAndUpdate(const std::vector<RefPtr<UINode>>& newNodes) override {
        // Key-based diff 算法
        PerformKeyBasedDiff(newNodes);
    }
};

2. 策略模式(循环策略)

定义:定义一系列算法,将每个算法封装起来,并使它们可以互换。

应用场景:不同的循环渲染策略

策略接口

class LoopStrategy {
public:
    virtual std::vector<RefPtr<UINode>> CreateNodes(
        const DataSource& dataSource
    ) = 0;

    virtual void UpdateNodes(
        std::vector<RefPtr<UINode>>& nodes,
        const DataSource& dataSource
    ) = 0;
};

具体策略

// 策略 1:全量加载策略(ForEach)
class EagerLoadingStrategy : public LoopStrategy {
public:
    std::vector<RefPtr<UINode>> CreateNodes(
        const DataSource& dataSource
    ) override {
        // 为所有数据项创建节点
        std::vector<RefPtr<UINode>> nodes;
        for (int i = 0; i < dataSource.TotalCount(); i++) {
            nodes.push_back(CreateNode(dataSource.GetData(i)));
        }
        return nodes;
    }
};

// 策略 2:懒加载策略(LazyForEach)
class LazyLoadingStrategy : public LoopStrategy {
public:
    std::vector<RefPtr<UINode>> CreateNodes(
        const DataSource& dataSource
    ) override {
        // 仅创建视口内的节点
        std::vector<RefPtr<UINode>> nodes;
        for (int i = viewportStart_; i <= viewportEnd_; i++) {
            nodes.push_back(CreateNode(dataSource.GetData(i)));
        }
        return nodes;
    }
};

// 策略 3:虚拟滚动策略(RepeatVirtualScroll)
class VirtualScrollStrategy : public LoopStrategy {
public:
    std::vector<RefPtr<UINode>> CreateNodes(
        const DataSource& dataSource
    ) override {
        // 创建视口节点 + 缓存节点
        std::vector<RefPtr<UINode>> nodes;
        // 上方缓存
        for (int i = viewportStart_ - upperCacheCount_; i < viewportStart_; i++) {
            nodes.push_back(GetOrCreateCachedNode(i));
        }
        // 视口节点
        for (int i = viewportStart_; i <= viewportEnd_; i++) {
            nodes.push_back(CreateNode(dataSource.GetData(i)));
        }
        // 下方缓存
        for (int i = viewportEnd_ + 1; i <= viewportEnd_ + lowerCacheCount_; i++) {
            nodes.push_back(GetOrCreateCachedNode(i));
        }
        return nodes;
    }
};

优势

  • 算法可互换(运行时切换策略)
  • 扩展性强(新增策略无需修改现有代码)
  • 测试友好(可独立测试每个策略)

3. 观察者模式(DataChangeListener)

定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。

应用场景:数据源变化监听

Subject(被观察者)

class IDataSource {
public:
    // 注册观察者
    virtual void RegisterDataChangeListener(DataChangeListener listener) = 0;

    // 注销观察者
    virtual void UnregisterDataChangeListener(DataChangeListener listener) = 0;

protected:
    // 通知所有观察者
    void NotifyDataChanged() {
        for (auto& listener : listeners_) {
            listener->OnDataChanged();
        }
    }

private:
    std::vector<DataChangeListener> listeners_;
};

Observer(观察者)

class DataChangeListener {
public:
    virtual void OnDataChanged() = 0;
    virtual void OnDataReloaded() = 0;
};

class LazyForEachNode : public DataChangeListener {
public:
    void OnDataChanged() override {
        // 数据变化时触发刷新
        RefreshAll();
    }

    void OnDataReloaded() override {
        // 数据重新加载时触发全量刷新
        ClearAll();
        RefreshAll();
    }
};

使用流程

1. 数据源注册监听器
   dataSource->RegisterDataChangeListener(lazyForEachNode)

2. 数据发生变化
   dataSource->AddItem(newItem)

3. 数据源通知所有观察者
   dataSource->NotifyDataChanged()

4. LazyForEachNode 收到通知,刷新 UI
   lazyForEachNode->OnDataChanged()
   ↓
   lazyForEachNode->RefreshAll()

优势

  • 解耦数据源和 UI 组件
  • 支持多个观察者
  • 响应式更新

4. Builder 模式(LazyForEachBuilder)

定义:将复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。

应用场景:LazyForEach 节点的构建

Builder 接口

class LazyForEachBuilder {
public:
    // 配置数据源
    LazyForEachBuilder& SetDataSource(const RefPtr<IDataSource>& source);

    // 配置子项生成器
    LazyForEachBuilder& SetItemGenerator(const ItemGenerator& gen);

    // 配置缓存参数
    LazyForEachBuilder& SetCachedCount(int count);

    // 构建最终对象
    RefPtr<LazyForEachNode> Build();

private:
    RefPtr<IDataSource> dataSource_;
    ItemGenerator itemGenerator_;
    int cachedCount_ = 0;
};

使用示例

// 使用 Builder 构建 LazyForEachNode
auto lazyForEachNode = LazyForEachBuilder()
    .SetDataSource(myDataSource)
    .SetItemGenerator([](const Item& item) {
        return Text(item.name);
    })
    .SetCachedCount(10)
    .Build();

优势

  • 分步构建(参数配置清晰)
  • 可读性强(链式调用)
  • 扩展性好(新增参数只需添加方法)

5. 享元模式(RepeatVirtualScrollCaches)

定义:运用共享技术有效地支持大量细粒度的对象。

应用场景:虚拟滚动中的节点复用

Flyweight Factory

class RepeatVirtualScrollCaches {
public:
    // 获取节点(优先从缓存池获取)
    RefPtr<UINode> GetNode(int index) {
        // 1. 尝试从缓存池获取
        if (auto node = TryGetFromCache(index)) {
            return node;
        }

        // 2. 缓存未命中,创建新节点
        auto node = CreateNode(index);

        // 3. 加入缓存池
        AddToCache(node);

        return node;
    }

    // 回收节点到缓存池
    void RecycleNode(const RefPtr<UINode>& node) {
        cachePool_.push_back(node);
    }

private:
    // 缓存池(共享的节点)
    std::vector<RefPtr<UINode>> cachePool_;

    // 视口上方的缓存
    std::vector<RefPtr<UINode>> upperCaches_;

    // 视口下方的缓存
    std::vector<RefPtr<UINode>> lowerCaches_;
};

优势

  • 减少内存占用(节点复用)
  • 提高性能(减少创建/销毁开销)
  • 支持大规模数据

性能特性对比

内存占用对比

组件 100 项 1,000 项 10,000 项 内存增长趋势
ForEach 100% 1000% 10000% 线性增长(O(n))
LazyForEach(缓存 10) 20% 20% 20% 恒定(视口 + 缓存)
Repeat(10 次) 10% 10% 10% 恒定(固定次数)
RepeatVirtualScroll(缓存 5) 15% 15% 15% 恒定(仅视口)

结论

  • ForEach:不适合大数据集(内存线性增长)
  • LazyForEach:适合中大规模数据(内存恒定)
  • RepeatVirtualScroll:适合超大规模数据(内存最低)

初始化速度对比

组件 100 项 1,000 项 10,000 项 时间复杂度
ForEach 10ms 100ms 1000ms O(n)
LazyForEach(缓存 10) 20ms 30ms 50ms O(1)(仅视口)
Repeat(10 次) 5ms 5ms 5ms O(1)
RepeatVirtualScroll 50ms 60ms 80ms O(1)(缓存初始化)

结论

  • ForEach:小数据集初始化最快
  • Repeat:固定次数最快
  • LazyForEach:中大数据集速度优秀
  • RepeatVirtualScroll:超大数据集性能稳定,但有初始开销

滚动性能对比

组件 FPS(100 项) FPS(1,000 项) FPS(10,000 项) 滚动流畅度
ForEach 60 30 5 大数据集卡顿
LazyForEach 60 60 55 流畅
Repeat 60 60 60 最流畅(固定)
RepeatVirtualScroll 60 60 60 最流畅(动态)

结论

  • ForEach:仅适合小数据集(< 100 项)
  • LazyForEach:中大数据集流畅(100 - 1000 项)
  • RepeatVirtualScroll:超大数据集最佳(1000+ 项)

更新性能对比

场景 ForEach LazyForEach Repeat RepeatVirtualScroll
单条数据更新 全量 diff(慢) 增量更新(快) 无更新 增量更新(快)
批量更新 全量 diff(慢) 增量更新(快) 无更新 增量更新(快)
全量替换 重建所有(慢) 重建视口(快) 重建所有(快) 重建视口(快)
条件切换 不适用 不适用 不适用 IfElse(最快)

结论

  • ForEach:全量 diff,性能较差
  • LazyForEach:增量更新,性能优秀
  • IfElse:条件切换性能最佳

快速导航

按使用场景选择组件

场景 推荐组件 理由 替代方案
小列表(< 100 项) ForEach 简单易用,性能优秀 LazyForEach(过度设计)
长列表(100 - 1000 项) LazyForEach 按需加载,内存可控 ForEach(内存高)
超长列表(1000+ 项) RepeatVirtualScroll 虚拟滚动,性能最佳 LazyForEach(内存较高)
固定次数重复 Repeat 极简实现,性能最佳 ForEach(不必要)
Tab 切换 IfElse 分支复用,切换快速 ForEach(不适用)
主题隔离 WithTheme 作用域隔离 不适用
动态内容 ContentSlot 灵活挂载/卸载 不适用

按性能特性选择组件

性能需求 推荐组件 权衡 注意事项
最低内存占用 RepeatVirtualScroll 初始开销高 需要配置缓存参数
最快初始化 ForEach(< 100 项) 内存占用高 仅适合小数据集
最流畅滚动 RepeatVirtualScroll 实现复杂 需要实现 DataSource
最简单开发 ForEach 不适合大数据 无需实现接口
最高扩展性 LazyForEach 需要实现接口 配置缓存参数

按数据特性选择组件

数据特性 推荐组件 数据源要求 更新机制
静态小数据 ForEach Iterable(数组) 全量 diff
动态大数据 LazyForEach IDataSource 增量更新
固定次数 Repeat number 无更新
超长列表 RepeatVirtualScroll IDataSource 虚拟滚动
条件分支 IfElse boolean/enum 分支切换

组件选择决策树

开始
    │
    ├─ 是否需要循环渲染?
    │   │
    │   ├─ 是 → 数据规模?
    │   │   │
    │   │   ├─ 小规模(< 100 项)→ ForEach
    │   │   │
    │   │   ├─ 中规模(100 - 1000 项)→ LazyForEach
    │   │   │
    │   │   └─ 超大规模(1000+ 项)→ RepeatVirtualScroll
    │   │
    │   └─ 否 → 是否需要条件渲染?
    │       │
    │       ├─ 是 → IfElse
    │       │
    │       └─ 否 → 是否需要主题隔离?
    │           │
    │           ├─ 是 → WithTheme
    │           │
    │           └─ 否 → ContentSlot

常见问题排查

问题 可能原因 解决方案
列表滚动卡顿 使用 ForEach 渲染大数据集 改用 LazyForEach 或 RepeatVirtualScroll
内存占用过高 ForEach 全量加载 改用 LazyForEach,配置缓存
初始化速度慢 RepeatVirtualScroll 缓存配置过大 减少缓存数量(cachedCount
数据更新不生效 未实现 IDataSource 监听器 实现 RegisterDataChangeListener
分支切换慢 IfElse 未启用复用 使用 TryRetake() 复用节点
样式冲突 全局主题污染 使用 WithTheme 隔离作用域

源码规模统计

整体规模

类别 文件数 总行数 平均行数/文件 占比
循环组件 8 4,200 525 70%
条件渲染 2 420 210 10%
特殊语法 4 600 150 10%
工具类 3 480 160 5%
Arkoala 支持 4 300 75 5%
总计 21 6,000 286 100%

循环组件详细统计

组件 H 文件 CPP 文件 总计 说明
ForEach 150 400 550 标准循环,代码量适中
LazyForEach 200 600 800 懒加载,逻辑较复杂
Repeat 80 150 230 固定循环,代码量最小
RepeatVirtualScroll 250 800 1,050 虚拟滚动,代码量最大
ForEachBaseNode(基类) 120 300 420 循环组件基类
LazyForEachBuilder 80 180 260 懒加载构建器
RepeatVirtualScrollCaches 100 250 350 虚拟滚动缓存系统
NodeContent 60 140 200 节点内容管理器
小计 1,040 2,820 3,860 -

:Arkoala 组件未计入循环组件统计,单独列出。


前端桥接层规模

前端 文件数 总行数 主要文件
声明式前端 5 1,200 js_for_each.cpp (300), js_lazy_foreach.cpp (350), js_repeat_virtual_scroll.cpp (300)
CJ 前端 3 500 lazy_foreach.cpp (200), cj_lazy_foreach_ffi.cpp (200)
总计 8 1,700 -

复杂度评估

组件 代码行数 圈复杂度 维护难度 备注
ForEach 550 适合入门学习
LazyForEach 800 中高 涉及懒加载逻辑
Repeat 230 最简单,适合学习
RepeatVirtualScroll 1,050 虚拟滚动,逻辑复杂
IfElse 420 分支管理,逻辑清晰
WithTheme 300 作用域管理,简单
ContentSlot 270 插槽管理,简单

相关文档链接

Syntax 模块知识库文档

文档 路径 说明
架构总览 docs/syntax/Syntax_Architecture_Overview.md 当前文档,模块全貌
ForEach 组件 docs/syntax/ForEach_Knowledge_Base.md ForEach 独立组件知识库
LazyForEach 组件 docs/syntax/LazyForEach_Knowledge_Base.md LazyForEach 独立组件知识库
Repeat 组件 docs/syntax/Repeat_Knowledge_Base.md Repeat 独立组件知识库
RepeatVirtualScroll 组件 docs/syntax/RepeatVirtualScroll_Knowledge_Base.md RepeatVirtualScroll 独立组件知识库
条件渲染 docs/syntax/Conditional_Rendering_Guide.md IfElse 条件渲染专题
特殊语法组件 docs/syntax/Special_Syntax_Components.md WithTheme、ContentSlot 专题
性能优化指南 docs/pattern/syntax/Performance_Optimization_Guide.md 性能优化最佳实践
前端桥接层 docs/pattern/syntax/Frontend_Bridge_Integration.md 前端集成详解

:部分专题文档尚未创建,将在后续工作中补充。


其他相关文档

文档 路径 说明
组件开发指南 docs/如何新增一个组件.md 新增组件的完整流程
Pattern 层设计 docs/architecture/Pattern_Layer_Design.md Pattern 层架构设计
性能优化指南 docs/best_practices/Performance_Optimization.md 通用性能优化指南

附录

术语表

术语 英文 说明
语法节点 Syntax Node 表示 ArkTS 语法结构的节点(如 ForEach, IfElse)
循环组件 Loop Component 实现循环渲染的组件(如 ForEach, LazyForEach)
懒加载 Lazy Loading 按需创建节点,仅在需要时才创建
虚拟滚动 Virtual Scroll 仅渲染视口内的节点,节点可复用
条件渲染 Conditional Rendering 根据条件动态切换显示内容
作用域 Scope 变量和样式的生效范围
数据源 Data Source 提供数据的接口(如 IDataSource
Diff 算法 Diff Algorithm 比较新旧节点差异的算法
Key-based Diff Key-based Diff 基于 Key 值的 Diff 算法,高效识别节点变化
缓存 Cache 临时存储已创建的节点,避免重复创建
视口 Viewport 当前可见的区域
分支 ID Branch ID 标识 IfElse 中的分支(0 = if, 1 = else)
桥接层 Bridge Layer 连接前端语言和后端组件的层

参考资源

官方文档

源码位置

  • Pattern 层:frameworks/core/components_ng/pattern/syntax/
  • 声明式前端:frameworks/bridge/declarative_frontend/jsview/
  • CJ 前端:frameworks/bridge/cj_frontend/cppview/

相关规范


版本历史

版本 时间 作者 变更说明
v1.0 2026-02-05 Claude Code 初始版本,完成架构总览

贡献指南

如何更新本文档

  1. 发现错误:使用 Grep/Read 工具验证实际代码,修正错误
  2. 补充内容:基于源码分析,补充缺失的内容
  3. 新增章节:根据模块演进,添加新的章节
  4. 更新示例:确保代码示例与实际代码一致

提交规范

  • 在文档头部更新版本号更新时间
  • 版本历史中记录变更说明
  • 引用实际代码时,标注文件路径和行号(如 for_each_node.cpp:123

结语

Syntax 模块是 ArkUI 框架中负责语法结构和控制流的核心模块,提供多样化的循环渲染、条件分支、作用域管理等关键能力。

核心价值

  • 灵活的控制流:支持多种循环策略和条件渲染
  • 高性能渲染:懒加载、虚拟滚动等优化技术
  • 多前端支持:声明式前端、CJ 前端、Arkoala 语言
  • 扩展性强:清晰的架构,易于扩展新语法

适用场景

  • 小列表 → ForEach(简单易用)
  • 长列表 → LazyForEach(按需加载)
  • 超长列表 → RepeatVirtualScroll(虚拟滚动)
  • Tab 切换 → IfElse(分支复用)
  • 主题隔离 → WithTheme(作用域管理)

学习路径

  1. 入门:从 RepeatForEach 开始,理解基本概念
  2. 进阶:学习 LazyForEachIfElse,掌握懒加载和条件渲染
  3. 高级:深入研究 RepeatVirtualScroll,理解虚拟滚动和缓存机制
  4. 实践:参考 如何新增一个组件.md,尝试新增自定义语法组件

持续演进

  • 欢迎贡献专题文档(如组件详解、性能优化指南)
  • 欢迎补充 Arkoala 语言支持的相关文档
  • 欢迎反馈文档中的错误和改进建议

文档结束

如有疑问或建议,请参考相关文档或联系文档维护者。