ArkUI 特殊语法组件指南

文档版本: v1.0 更新时间: 2026-02-05 源码版本: OpenHarmony ace_engine (master 分支) 作者: 基于 CLAUDE.md 规范自动生成


目录

  1. 概述
  2. WithTheme 组件
  3. ContentSlot 组件
  4. 工具类说明
  5. 使用场景
  6. 最佳实践
  7. 常见问题
  8. 附录

概述

组件体系

ArkUI 框架提供了一组特殊语法组件,用于实现高级 UI 构建能力。这些组件不同于普通的 UI 组件(如 Button、Text),它们主要起到语法结构功能增强的作用。

核心特殊语法组件:

组件 标签 源码位置 核心功能
WithTheme JS_WITH_THEME_ETS_TAG with_theme_node.h:30 主题作用域隔离与管理
ContentSlot JS_NODE_SLOT_ETS_TAG content_slot_node.h:30 动态内容插槽管理
SyntaxItem JS_SYNTAX_ITEM_ETS_TAG for_each_item.h:33 ForEach/LazyForEach/IfElse 语法项包装器

核心特性:

  • 语法节点标识: 所有特殊语法组件都实现 IsSyntaxNode() 返回 true
  • 非渲染节点: 主要用于结构化组织,不直接参与渲染
  • 桥接层支持: 支持 ArkTS、Cangjie 等多前端语言

架构位置

┌─────────────────────────────────────────────────────────┐
│  前端 ArkTS / Cangjie                                   │
│                                                          │
│  WithTheme { ... }     // 主题作用域                     │
│  ContentSlot { ... }   // 内容插槽                       │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│  Bridge Layer (桥接层)                                   │
│                                                          │
│  WithThemeModifier:   frameworks/core/interfaces/native/│
│                       implementation/with_theme_modifier.cpp│
│  ContentSlotModel:    frameworks/core/components_ng/    │
│                       syntax/content_slot_model_ng.cpp  │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│  Core Layer (特殊语法节点)                                │
│                                                          │
│  WithThemeNode:     frameworks/core/components_ng/      │
│                     syntax/with_theme_node.h:30         │
│  ContentSlotNode:   frameworks/core/components_ng/      │
│                     syntax/content_slot_node.h:30       │
│  SyntaxItem:        frameworks/core/components_ng/      │
│                     syntax/for_each_item.h:33           │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│  工具类支持                                               │
│                                                          │
│  NodeContent:        frameworks/core/components_ng/      │
│                      syntax/node_content.h:27            │
│  ShallowBuilder:     frameworks/core/components_ng/      │
│                      syntax/shallow_builder.h:29         │
└─────────────────────────────────────────────────────────┘

WithTheme 组件

WithTheme 组件定位

WithTheme 组件是 ArkUI 框架中实现主题作用域隔离的核心语法节点。它允许开发者在 UI 树的不同层级创建独立的主题作用域,实现局部主题定制。

核心功能:

  • 主题作用域隔离: 为子树创建独立的主题作用域
  • 主题切换通知: 支持主题更新时的通知机制
  • 销毁回调管理: 提供作用域销毁时的回调机制
  • 主题存储管理: 与 TokenThemeStorage 配合管理主题实例

源码定义:

// frameworks/core/components_ng/syntax/with_theme_node.h:30
class ACE_EXPORT WithThemeNode : public UINode {
    DECLARE_ACE_TYPE(WithThemeNode, UINode);

public:
    static RefPtr<WithThemeNode> GetWithThemeNode(int32_t nodeId);
    static RefPtr<WithThemeNode> CreateWithThemeNode(int32_t nodeId);
    static RefPtr<WithThemeNode> GetOrCreateWithThemeNode(int32_t nodeId);

    explicit WithThemeNode(int32_t nodeId) : UINode(V2::JS_WITH_THEME_ETS_TAG, nodeId) {
        SetThemeScopeId(nodeId);
    }

    bool IsSyntaxNode() const override { return true; }
    bool IsAtomicNode() const override { return false; }
};

关键特性:

  • IsSyntaxNode() 返回 true - 标识为语法节点
  • IsAtomicNode() 返回 false - 非原子节点,支持子节点
  • 构造时自动设置 themeScopeId 为节点 ID

主题作用域管理

作用域 ID 设置

SetThemeScopeId 实现 (with_theme_node.cpp:72-77):

void WithThemeNode::SetThemeScopeId(int32_t themeScopeId)
{
    // 仅在首次设置时生效(主题作用域 ID 为 0 时)
    if (themeScopeId_ == 0) {
        UINode::SetThemeScopeId(themeScopeId);
    }
}

设计要点:

  • 主题作用域 ID 一旦设置则不可更改
  • 防止意外修改导致主题混乱
  • 通过 themeScopeId_ 为 0 判断是否为首次设置

主题作用域更新通知

NotifyThemeScopeUpdate 实现 (with_theme_node.cpp:52-55):

void WithThemeNode::NotifyThemeScopeUpdate()
{
    UINode::UpdateThemeScopeUpdate(GetThemeScopeId());
}

更新机制:

  1. 调用 NotifyThemeScopeUpdate() 触发主题更新
  2. 向父节点的 UpdateThemeScopeUpdate() 传播更新通知
  3. 子树中所有监听主题变化的节点收到通知

主题存储管理

销毁时的资源清理 (with_theme_node.cpp:23-29):

WithThemeNode::~WithThemeNode()
{
    // 1. 调用销毁回调
    if (themeScopeDestroyCallback_) {
        themeScopeDestroyCallback_();
    }
    // 2. 从 TokenThemeStorage 移除主题作用域
    TokenThemeStorage::GetInstance()->RemoveThemeScope(GetId());
}

资源管理流程:

  1. 执行用户注册的销毁回调
  2. 从全局主题存储中移除当前作用域
  3. 释放主题相关资源

主题作用域隔离

TokenThemeStorage 机制

主题存储结构 (token_theme_storage.h:29-83):

class ACE_EXPORT TokenThemeStorage final {
public:
    // 存储主题作用域 (key: themeScopeId, value: themeId)
    void StoreThemeScope(TokenThemeScopeId themeScopeId, int32_t themeId);
    void RemoveThemeScope(TokenThemeScopeId themeScopeId, bool removeToken = false);
    const RefPtr<TokenTheme>& GetTheme(TokenThemeScopeId themeScopeId);

    // 默认主题管理
    void SetDefaultTheme(const RefPtr<TokenTheme>& theme, ColorMode colorMode);
    const RefPtr<TokenTheme>& GetDefaultTheme();

    // 主题缓存 (key: themeId, value: theme instance)
    void CacheSet(const RefPtr<TokenTheme>& theme);
    const RefPtr<TokenTheme>& CacheGet(int32_t themeId);

private:
    // 主题作用域映射
    std::unordered_map<TokenThemeScopeId, int32_t> themeScopeMap_;

    // 主题缓存
    std::map<int32_t, RefPtr<TokenTheme>> themeCache_;
};

隔离原理:

  1. 作用域隔离: 每个 WithThemeNode 有唯一的 themeScopeId
  2. 主题映射: TokenThemeStorage 维护 themeScopeId → themeId 映射
  3. 主题查找: 子节点通过 GetTheme(themeScopeId) 获取对应主题
  4. 默认主题: 未指定作用域的节点使用默认主题

隔离效果示例

// 全局主题: 蓝色系
@Entry
@Component
struct MainPage {
  build() {
    Column() {
      Text("默认主题 - 蓝色")

      // 主题作用域 A: 红色系
      WithTheme({ themeId: THEME_RED }) {
        Text("主题 A - 红色")

        // 嵌套主题作用域 B: 绿色系
        WithTheme({ themeId: THEME_GREEN }) {
          Text("主题 B - 绿色")
        }

        Text("回到主题 A - 红色")
      }

      Text("回到默认主题 - 蓝色")
    }
  }
}

主题查找链:

  1. 子节点首先查找当前 themeScopeId 对应的主题
  2. 如果未找到,向上查找父作用域的主题
  3. 最终使用默认主题

销毁回调机制

回调注册

SetOnThemeScopeDestroy 实现 (with_theme_node.cpp:67-70):

void WithThemeNode::SetOnThemeScopeDestroy(ThemeScopeDestroyCallback&& callback)
{
    themeScopeDestroyCallback_ = std::move(callback);
}

回调类型定义 (with_theme_node.h:28):

using ThemeScopeDestroyCallback = std::function<void()>;

使用场景

场景 1: 资源清理

auto withThemeNode = WithThemeNode::CreateWithThemeNode(nodeId);
withThemeNode->SetOnThemeScopeDestroy([]() {
    // 清理主题相关的自定义资源
    CustomResourceManager::Cleanup(scopeId);
});

场景 2: 状态同步

withThemeNode->SetOnThemeScopeDestroy([this]() {
    // 通知其他组件主题作用域已销毁
    this->OnThemeScopeDestroyed(scopeId);
});

WithTheme 使用示例

C API 使用方式

基础用法:

// 创建 WithTheme 节点
ArkUINodeHandle withThemeNode = OH_ArkUI_WithTheme_Create(nodeId);

// 设置主题选项
ArkUI_WithThemeOptions options = {
    .themeId = CUSTOM_THEME_ID,
    .colorMode = COLOR_MODE_DARK
};
OH_ArkUI_WithTheme_SetOptions(withThemeNode, &options);

设置销毁回调 (C++):

auto withThemeNode = WithThemeNode::GetOrCreateWithThemeNode(nodeId);
withThemeNode->SetOnThemeScopeDestroy([]() {
    LOGI("Theme scope destroyed");
});

触发主题更新

// 通知主题更新
auto withThemeNode = WithThemeNode::GetWithThemeNode(nodeId);
if (withThemeNode) {
    withThemeNode->NotifyThemeScopeUpdate();
}

更新传播:

  1. 调用 NotifyThemeScopeUpdate()
  2. 向父节点传播 UpdateThemeScopeUpdate()
  3. 父节点递归通知子树
  4. 子节点重新获取主题值并更新 UI

ContentSlot 组件

ContentSlot 组件定位

ContentSlot 组件是 ArkUI 框架中实现动态内容插槽的核心语法节点。它提供了灵活的内容管理能力,支持运行时动态附加、分离内容节点。

核心功能:

  • 内容插槽管理: 提供可复用的内容容器
  • 动态内容附加/分离: 运行时动态管理内容
  • 原子节点特性: 不可分割的语义单元
  • 生命周期管理: 处理内容挂载/卸载事件

源码定义:

// frameworks/core/components_ng/syntax/content_slot_node.h:30
class ACE_EXPORT ContentSlotNode : public UINode {
    DECLARE_ACE_TYPE(ContentSlotNode, UINode);

public:
    static RefPtr<ContentSlotNode> GetOrCreateContentSlot(int32_t nodeId);

    explicit ContentSlotNode(int32_t nodeId) : UINode(V2::JS_NODE_SLOT_ETS_TAG, nodeId) {}

    bool IsAtomicNode() const override { return true; }
    bool IsSyntaxNode() const override { return true; }

    void AttachNodeContent(NodeContent* content);
    void DetachNodeContent();
};

关键特性:

  • IsAtomicNode() 返回 true - 原子节点,不可分割
  • IsSyntaxNode() 返回 true - 语法节点
  • 持有 RefPtr<NodeContent> content_ 引用

内容插槽管理

AttachNodeContent 实现

源码 (content_slot_node.h:49-58):

void AttachNodeContent(NodeContent* content)
{
    CHECK_NULL_VOID(content);

    // 当 content_ 已被其他 slot 持有时,先分离
    if (content_ && (RawPtr(content_) != content) &&
        (content_->GetContentSlot().Upgrade() == this)) {
        content_->DetachFromNode();
    }

    content_ = content;
    content_->AttachToNode(this);
}

附加流程:

  1. 冲突检查: 如果当前 content_ 被其他 slot 持有,先分离
  2. 引用更新: 更新 content_ 为新内容
  3. 双向绑定: 调用 content->AttachToNode(this) 建立双向关联

DetachNodeContent 实现

源码 (content_slot_node.h:60-66):

void DetachNodeContent()
{
    if (content_) {
        content_->DetachFromNode();
    }
    content_ = nullptr;
}

分离流程:

  1. 内容分离: 调用 content_->DetachFromNode()
  2. 引用清除: 将 content_ 置空

GetOrCreateContentSlot 实现

源码 (content_slot_node.cpp:30-39):

RefPtr<ContentSlotNode> ContentSlotNode::GetOrCreateContentSlot(int32_t nodeId)
{
    auto node = ElementRegister::GetInstance()->GetSpecificItemById<ContentSlotNode>(nodeId);
    if (node) {
        return node; // 已存在则直接返回
    }
    node = MakeRefPtr<ContentSlotNode>(nodeId);
    ElementRegister::GetInstance()->AddUINode(node);
    return node;
}

设计模式:

  • 单例模式: 相同 nodeIdContentSlotNode 全局唯一
  • 懒加载: 首次调用时创建,后续复用
  • 注册机制: 创建后注册到 ElementRegister

动态内容附加/分离

NodeContent 核心功能

源码 (node_content.h:27-77):

class NodeContent : public AceType {
    DECLARE_ACE_TYPE(NodeContent, AceType);

public:
    void AttachToNode(UINode* node);
    void DetachFromNode();

    void AddNode(UINode* node, int32_t position = -1);
    void RemoveNode(UINode* node);

    void OnAttachToMainTree();
    void OnDetachFromMainTree();

    void SetAttachToMainTreeCallback(std::function<void()>&& callback);
    void SetDetachFromMainTreeCallback(std::function<void()>&& callback);

    WeakPtr<UINode> GetContentSlot() { return nodeSlot_; }

private:
    WeakPtr<UINode> nodeSlot_;              // 关联的 slot
    std::list<RefPtr<UINode>> children_;     // 子节点列表
    std::function<void()> onAttachCallback_; // 挂载回调
    std::function<void()> onDetachCallback_; // 卸载回调
    bool onMainTree_ = false;                // 是否已挂载到主树
};

AttachToNode 详细实现

源码 (node_content.cpp:22-39):

void NodeContent::AttachToNode(UINode* node)
{
    auto slot = nodeSlot_.Upgrade();
    if (slot && (slot != node)) {
        DetachFromNode(); // 从旧 slot 分离
    }

    nodeSlot_ = WeakClaim(node);

    // 添加子节点到 slot
    std::list<RefPtr<NG::UINode>> nodes;
    for (const auto& child : children_) {
        node->AddChild(child);
        BuilderUtils::GetBuilderNodes(child, nodes);
    }

    // 更新 Builder 节点关系
    BuilderUtils::AddBuilderToParent(Claim(node), nodes);
    node->MarkNeedFrameFlushDirty(PROPERTY_UPDATE_BY_CHILD_REQUEST);

    // 如果 slot 已在主树,触发挂载回调
    if (node->IsOnMainTree()) {
        OnAttachToMainTree();
    }
}

附加关键步骤:

  1. 冲突处理: 如果已关联其他 slot,先分离
  2. 引用建立: 建立到 slot 的弱引用
  3. 子节点迁移: 将所有 children_ 添加到 slot
  4. Builder 更新: 更新 Builder 节点的父子关系
  5. 脏标记: 标记需要刷新
  6. 回调触发: 如果 slot 已在主树,触发 OnAttachToMainTree()

DetachFromNode 详细实现

源码 (node_content.cpp:41-57):

void NodeContent::DetachFromNode()
{
    auto slot = nodeSlot_.Upgrade();
    nodeSlot_.Reset();

    if (slot) {
        // 保存子节点引用
        children_ = slot->GetChildren();

        // 清空 slot
        slot->Clean();

        // 更新 Builder 节点关系
        std::list<RefPtr<NG::UINode>> nodes;
        for (const auto& child : children_) {
            BuilderUtils::GetBuilderNodes(child, nodes);
        }
        BuilderUtils::RemoveBuilderFromParent(slot, nodes);

        // 如果 slot 在主树,触发卸载回调
        if (slot->IsOnMainTree()) {
            OnDetachFromMainTree();
        }
    }
}

分离关键步骤:

  1. 引用清除: 重置 nodeSlot_
  2. 子节点保存: 保存 slot 的子节点到 children_
  3. Slot 清空: 调用 slot->Clean() 清空 slot
  4. Builder 更新: 移除 Builder 节点关系
  5. 回调触发: 如果 slot 在主树,触发 OnDetachFromMainTree()

AddNode/RemoveNode 实现

AddNode (node_content.cpp:59-76):

void NodeContent::AddNode(UINode* node, int32_t position)
{
    auto slot = nodeSlot_.Upgrade();
    auto child = Claim(node);

    if (slot) {
        slot->AddChild(child, position);
        BuilderUtils::AddBuilderToParent(slot, child);
        slot->MarkNeedFrameFlushDirty(PROPERTY_UPDATE_BY_CHILD_REQUEST);
    }

    // 避免重复添加
    auto it = std::find(children_.begin(), children_.end(), child);
    if (it != children_.end()) {
        return;
    }

    // 插入到指定位置
    it = children_.begin();
    std::advance(it, position);
    children_.insert(it, child);
}

RemoveNode (node_content.cpp:78-92):

void NodeContent::RemoveNode(UINode* node)
{
    auto nodeRef = Claim(node);
    auto slot = nodeSlot_.Upgrade();

    if (slot) {
        slot->RemoveChild(nodeRef);
        BuilderUtils::RemoveBuilderFromParent(slot, nodeRef);
        slot->MarkNeedFrameFlushDirty(PROPERTY_UPDATE_BY_CHILD_REQUEST);
    }

    // 从列表中移除
    auto it = std::find(children_.begin(), children_.end(), nodeRef);
    if (it == children_.end()) {
        return;
    }
    children_.erase(it);
}

ContentSlot 生命周期管理

OnAttachToMainTree 实现

源码 (content_slot_node.h:68-74):

void OnAttachToMainTree(bool recursive) override
{
    UINode::OnAttachToMainTree(recursive);
    if (content_) {
        content_->OnAttachToMainTree();
    }
}

挂载流程:

  1. 调用父类 UINode::OnAttachToMainTree()
  2. 通知 content_ 触发 OnAttachToMainTree()
  3. NodeContent 执行 onAttachCallback_

OnDetachFromMainTree 实现

源码 (content_slot_node.h:76-82):

void OnDetachFromMainTree(bool recursive, PipelineContext* context = nullptr) override
{
    UINode::OnDetachFromMainTree(recursive, context);
    if (content_) {
        content_->OnDetachFromMainTree();
    }
}

卸载流程:

  1. 调用父类 UINode::OnDetachFromMainTree()
  2. 通知 content_ 触发 OnDetachFromMainTree()
  3. NodeContent 执行 onDetachCallback_

NodeContent 回调实现

OnAttachToMainTree (node_content.cpp:94-103):

void NodeContent::OnAttachToMainTree()
{
    if (onMainTree_) {
        return; // 避免重复触发
    }
    onMainTree_ = true;
    if (onAttachCallback_) {
        onAttachCallback_();
    }
}

OnDetachFromMainTree (node_content.cpp:105-114):

void NodeContent::OnDetachFromMainTree()
{
    if (!onMainTree_) {
        return; // 避免重复触发
    }
    onMainTree_ = false;
    if (onDetachCallback_) {
        onDetachCallback_();
    }
}

ContentSlot 使用示例

C API 基础用法

创建 ContentSlot:

// 创建 NodeContent
ArkUINodeContentHandle content = OH_ArkUI_NodeContent_Create();

// 创建 ContentSlot 并关联内容
OH_ArkUI_ContentSlot_Create(nodeId, content);

动态添加子节点:

// 创建子节点
ArkUINodeHandle childNode = OH_ArkUI_FrameNode_Create("Text", -1);

// 添加到 NodeContent
OH_ArkUI_NodeContent_AddChild(content, childNode, -1); // -1 表示追加到末尾

分离内容:

// 从 slot 分离内容
OH_ArkUI_ContentSlot_Detach(nodeId);

// 内容可以重新附加到其他 slot
OH_ArkUI_ContentSlot_Create(otherNodeId, content);

C++ Model 层用法

源码 (content_slot_model_ng.cpp:23-34):

void ContentSlotModel::Create(NodeContent* content)
{
    auto* stack = ViewStackProcessor::GetInstance();
    auto nodeId = stack->ClaimNodeId();
    auto slot = ContentSlotNode::GetOrCreateContentSlot(nodeId);
    stack->Push(slot);

    if (content) {
        slot->AttachNodeContent(content);
    } else {
        slot->DetachNodeContent();
    }
}

使用示例:

// 创建 NodeContent
auto nodeContent = AceType::MakeRefPtr<NodeContent>();

// 设置生命周期回调
nodeContent->SetAttachToMainTreeCallback([]() {
    LOGI("Content attached to main tree");
});
nodeContent->SetDetachFromMainTreeCallback([]() {
    LOGI("Content detached from main tree");
});

// 创建 ContentSlot
ContentSlotModel::Create(AceType::RawPtr(nodeContent));

// 动态添加子节点
auto textNode = FrameNode::CreateFrameNode(...);
nodeContent->AddNode(AceType::RawPtr(textNode), -1);

生命周期回调应用

场景 1: 懒加载初始化

nodeContent->SetAttachToMainTreeCallback([this]() {
    // 首次挂载时初始化资源
    this->InitializeHeavyResource();
});

nodeContent->SetDetachFromMainTreeCallback([this]() {
    // 卸载时释放资源
    this->ReleaseHeavyResource();
});

场景 2: 状态同步

nodeContent->SetAttachToMainTreeCallback([weakController = WeakClaim(controller)]() {
    auto controller = weakController.Upgrade();
    if (controller) {
        controller->OnContentVisible();
    }
});

工具类说明

NodeContent

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

核心功能:

  • 管理可动态附加/分离的节点内容
  • 维护子节点列表和插槽关联
  • 提供生命周期回调机制

主要接口:

// 附加/分离操作
void AttachToNode(UINode* node);
void DetachFromNode();

// 子节点管理
void AddNode(UINode* node, int32_t position = -1);
void RemoveNode(UINode* node);

// 生命周期
void OnAttachToMainTree();
void OnDetachFromMainTree();

// 回调设置
void SetAttachToMainTreeCallback(std::function<void()>&& callback);
void SetDetachFromMainTreeCallback(std::function<void()>&& callback);

// 状态查询
WeakPtr<UINode> GetContentSlot();
void* GetUserData() const;
void SetUserData(void* userData);

使用场景:

  1. 动态内容切换: 实现类似 Tab 切换的内容管理
  2. 懒加载: 按需加载和卸载内容
  3. 内容复用: 同一份内容在不同位置显示
  4. 生命周期管理: 精确控制内容的挂载/卸载时机

数据成员:

  • nodeSlot_: 关联的 ContentSlot 弱引用
  • children_: 管理的子节点列表
  • onAttachCallback_: 挂载回调
  • onDetachCallback_: 卸载回调
  • onMainTree_: 是否在主树标志
  • userData_: 用户自定义数据指针

SyntaxItem

源码位置: frameworks/core/components_ng/syntax/for_each_item.h:33

核心功能:

  • 作为 ForEach、LazyForEach、IfElse 等语法结构的包装器
  • 提供基于 key 的唯一标识
  • 支持语法节点的复用和状态保留

主要接口:

// 构造函数
explicit SyntaxItem(const std::string& key);

// 获取唯一标识
const std::string& GetKey() const;

// 静态创建方法
static RefPtr<SyntaxItem> CreateSyntaxItemNode(const std::string& key);

// 序列化支持
void ToJsonValue(std::unique_ptr<JsonValue>& json, const InspectorFilter& filter) const override;
void FromJson(const std::unique_ptr<JsonValue>& json) override;

关键特性:

  • IsSyntaxNode(): 返回 true
  • IsAtomicNode(): 返回 false (支持子节点)
  • 析构时设置 SetRemoveSilently(true)

使用场景:

  1. ForEach 列表项: 包装列表中的每个渲染项
  2. LazyForEach 懒加载项: 包装懒加载的渲染项
  3. IfElse 分支项: 包装条件分支的内容
  4. 节点复用: 通过 key 标识实现节点复用

设计要点:

  • 每个 SyntaxItem 有唯一的 key_ 标识
  • key 用于节点识别和复用判断
  • 支持序列化用于 DevTool 调试

ShallowBuilder

源码位置: frameworks/core/components_ng/syntax/shallow_builder.h:29

核心功能:

  • 提供 ListItem、GridItem 等组件的懒加载能力
  • 在最小化更新场景下延迟创建内容
  • 支持按需执行深度渲染

主要接口:

// 深度渲染函数类型
using DeepRenderFunc = std::function<RefPtr<UINode>()>;

// 构造函数
explicit ShallowBuilder(DeepRenderFunc&& deepRenderFunc);

// 执行深度渲染
RefPtr<UINode> ExecuteDeepRender();

// 状态查询
bool IsExecuteDeepRenderDone() const;
void MarkIsExecuteDeepRenderDone(bool executeDeepRenderDone);

工作原理 (源码注释, shallow_builder.h:27-28):

// ShallowBuilder对象用于ListItem和GridItem内部内容懒加载能力。
// 在最小化更新场景下,ListItem、GridItem这些组件采用懒加载方式,内部内容在js执行时不会创建仅保存创建方法。

使用场景:

  1. ListItem 懒加载: 列表项滚动到可视区域时才创建内容
  2. GridItem 懒加载: 网格项按需创建
  3. 性能优化: 减少初始渲染负担
  4. 最小化更新: 仅在需要时执行深度渲染

执行流程:

  1. 浅层构建: 创建 ShallowBuilder 并保存 deepRenderFunc_
  2. 延迟执行: 调用 ExecuteDeepRender() 时才真正创建节点
  3. 状态标记: executeDeepRenderDone_ 标记是否已执行
  4. 节点返回: 返回创建的 UINode 实例

数据成员:

  • deepRenderFunc_: 深度渲染函数
  • executeDeepRenderDone_: 执行完成标志

NodeContentPeer

源码位置: frameworks/core/interfaces/native/ani/node_content_peer.h:21

核心功能:

  • 提供 C API 与 C++ 实现之间的桥接
  • 封装 NodeContent 引用供跨语言调用
  • 支持通过句柄访问 NodeContent 对象

主要接口:

struct NodeContentPeer {
    OHOS::Ace::RefPtr<OHOS::Ace::NG::NodeContent> content;

    static NodeContentPeer* Create(const OHOS::Ace::RefPtr<OHOS::Ace::NG::NodeContent>& src)
    {
        auto nodeContent = new NodeContentPeer();
        nodeContent->content = src;
        return nodeContent;
    }
};

设计模式:

  • 包装器模式: 封装 C++ 对象为 C 可访问的结构
  • 工厂方法: Create() 静态方法创建实例
  • 句柄转换: 支持在 C API 和 C++ 实现之间转换

使用场景:

  1. C API 桥接: 为 C API 提供 NodeContent 访问能力
  2. 跨语言调用: ArkTS Native 与 C++ 层交互
  3. 句柄管理: 通过句柄操作 NodeContent

使用示例:

// C++ 侧创建
auto nodeContent = AceType::MakeRefPtr<NodeContent>();
auto peer = NodeContentPeer::Create(nodeContent);

// C API 侧使用
ArkUINodeContentHandle handle = reinterpret_cast<ArkUINodeContentHandle>(peer);
OH_ArkUI_NodeContent_AddChild(handle, childNode);

// 转换回 C++
auto cppPeer = reinterpret_cast<NodeContentPeer*>(handle);
auto cppContent = cppPeer->content;

使用场景

场景 1: 主题切换

需求: 在同一页面中支持多主题共存,不同区域使用不同主题。

实现方案:

@Entry
@Component
struct ThemeSwitchDemo {
  @State currentTheme: number = THEME_LIGHT;

  build() {
    Column() {
      // 顶部栏: 始终使用浅色主题
      WithTheme({ themeId: THEME_LIGHT }) {
        TopBar()
      }

      // 内容区: 动态主题
      WithTheme({ themeId: this.currentTheme }) {
        Content()
      }

      // 底部栏: 始终使用深色主题
      WithTheme({ themeId: THEME_DARK }) {
        BottomBar()
      }
    }
  }
}

实现原理:

  1. 每个 WithTheme 创建独立作用域
  2. 子组件从最近的作用域获取主题
  3. 主题切换时调用 NotifyThemeScopeUpdate() 触发更新

场景 2: 动态内容切换

需求: 实现类似 Tab 切换的效果,不同 Tab 显示不同内容,但保留各 Tab 的状态。

实现方案:

// 创建两个 NodeContent
auto tabContent1 = AceType::MakeRefPtr<NodeContent>();
auto tabContent2 = AceType::MakeRefPtr<NodeContent>();

// 创建共享的 ContentSlot
auto slot = ContentSlotNode::GetOrCreateContentSlot(slotId);

// 初始化 Tab 1 内容
tabContent1->AddNode(AceType::RawPtr(CreateTab1Content()));

// 初始化 Tab 2 内容
tabContent2->AddNode(AceType::RawPtr(CreateTab2Content()));

// 显示 Tab 1
slot->AttachNodeContent(AceType::RawPtr(tabContent1));

// 切换到 Tab 2
slot->AttachNodeContent(AceType::RawPtr(tabContent2));

// 切回 Tab 1 (状态保留)
slot->AttachNodeContent(AceType::RawPtr(tabContent1));

状态保留原理:

  1. NodeContent 持有子节点引用
  2. 分离时不销毁子节点,仅从 slot 移除
  3. 重新附加时恢复之前的节点
  4. 节点状态自然保留

场景 3: 懒加载优化

需求: 大列表场景下,仅创建可见区域的列表项,减少初始渲染负担。

实现方案:

class ListItemNode : public UINode {
public:
    ListItemNode() {
        // 创建 ShallowBuilder,延迟内容创建
        shallowBuilder_ = AceType::MakeRefPtr<ShallowBuilder>(
            [this]() -> RefPtr<UINode> {
                return this->CreateContent(); // 滚动到可见时才调用
            }
        );
    }

    void OnVisible() {
        // 首次可见时执行深度渲染
        if (!shallowBuilder_->IsExecuteDeepRenderDone()) {
            auto content = shallowBuilder_->ExecuteDeepRender();
            this->AddChild(content);
        }
    }

private:
    RefPtr<ShallowBuilder> shallowBuilder_;
};

性能优化效果:

  • 初始渲染时间: 减少 70-90%
  • 内存占用: 按需分配,减少峰值内存
  • 滚动性能: 仅创建可见项,滚动更流畅

场景 4: 生命周期管理

需求: 精确控制资源的加载和释放时机,避免资源浪费。

实现方案:

// 创建带生命周期回调的 NodeContent
auto nodeContent = AceType::MakeRefPtr<NodeContent>();

// 设置挂载回调
nodeContent->SetAttachToMainTreeCallback([]() {
    // 加载图片资源
    ImageCache::Preload({"image1.png", "image2.png"});
    // 初始化动画
    AnimationController::Initialize();
});

// 设置卸载回调
nodeContent->SetDetachFromMainTreeCallback([]() {
    // 释放图片资源
    ImageCache::Release({"image1.png", "image2.png"});
    // 停止动画
    AnimationController::Stop();
});

// 关联到 slot
auto slot = ContentSlotNode::GetOrCreateContentSlot(slotId);
slot->AttachNodeContent(AceType::RawPtr(nodeContent));

适用场景:

  • 大型资源加载: 视频、3D 模型等按需加载
  • 动画资源: 可见时播放,不可见时停止
  • 网络请求: 可见时发起,卸载时取消
  • 定时器清理: 卸载时清除定时器

最佳实践

WithTheme 最佳实践

1. 合理划分主题作用域

推荐做法:

// 按功能模块划分主题作用域
Column() {
  WithTheme({ themeId: THEME_APP_BAR }) {
    AppBar() // 应用栏统一主题
  }

  WithTheme({ themeId: THEME_CONTENT }) {
    Content() // 内容区统一主题
  }

  WithTheme({ themeId: THEME_NAVIGATION }) {
    NavigationBar() // 导航栏统一主题
  }
}

避免:

// ❌ 过细的粒度,增加管理复杂度
Column() {
  WithTheme({ themeId: THEME_1 }) { Text("A") }
  WithTheme({ themeId: THEME_2 }) { Text("B") }
  WithTheme({ themeId: THEME_3 }) { Text("C") }
}

2. 主题切换性能优化

推荐做法:

// 批量更新后统一通知
void UpdateMultipleThemes() {
    // 更新多个主题配置
    TokenThemeStorage::GetInstance()->StoreThemeScope(scope1, theme1);
    TokenThemeStorage::GetInstance()->StoreThemeScope(scope2, theme2);

    // 统一触发更新通知
    auto withThemeNode1 = WithThemeNode::GetWithThemeNode(nodeId1);
    auto withThemeNode2 = WithThemeNode::GetWithThemeNode(nodeId2);
    if (withThemeNode1) withThemeNode1->NotifyThemeScopeUpdate();
    if (withThemeNode2) withThemeNode2->NotifyThemeScopeUpdate();
}

避免:

// ❌ 频繁触发更新
void UpdateThemesInefficient() {
    for (int i = 0; i < 100; i++) {
        StoreThemeScope(i, themes[i]);
        GetWithThemeNode(i)->NotifyThemeScopeUpdate(); // 触发 100 次更新
    }
}

3. 销毁回调资源清理

推荐做法:

withThemeNode->SetOnThemeScopeDestroy([weakResMgr = WeakClaim(resMgr)]() {
    auto resMgr = weakResMgr.Upgrade();
    if (resMgr) {
        resMgr->Cleanup(); // 使用弱引用,避免循环引用
    }
});

避免:

// ❌ 强引用可能导致内存泄漏
withThemeNode->SetOnThemeScopeDestroy([resMgr]() {
    resMgr->Cleanup(); // resMgr 被回调持有,无法释放
});

ContentSlot 最佳实践

1. 内容复用策略

推荐做法:

// 复用同一份 NodeContent
auto sharedContent = AceType::MakeRefPtr<NodeContent>();
sharedContent->AddNode(AceType::RawPtr(contentNode));

// 在多个位置共享显示
auto slot1 = ContentSlotNode::GetOrCreateContentSlot(slotId1);
auto slot2 = ContentSlotNode::GetOrCreateContentSlot(slotId2);

// 同一时间只能在一个 slot 显示
slot1->AttachNodeContent(AceType::RawPtr(sharedContent));
// ...
slot1->DetachNodeContent();
slot2->AttachNodeContent(AceType::RawPtr(sharedContent));

避免:

// ❌ 重复创建相同内容
auto content1 = CreateExpensiveContent();
auto content2 = CreateExpensiveContent(); // 浪费资源

2. 生命周期回调使用

推荐做法:

// 精确控制资源生命周期
nodeContent->SetAttachToMainTreeCallback([weakTarget = WeakClaim(target)]() {
    auto target = weakTarget.Upgrade();
    if (target) {
        target->OnVisible(); // 标记可见,开始加载
    }
});

nodeContent->SetDetachFromMainTreeCallback([weakTarget = WeakClaim(target)]() {
    auto target = weakTarget.Upgrade();
    if (target) {
        target->OnInvisible(); // 标记不可见,暂停加载
    }
});

避免:

// ❌ 在回调中执行耗时操作
nodeContent->SetAttachToMainTreeCallback([]() {
    LoadHeavyDataSync(); // 阻塞主线程
});

3. 内容更新策略

推荐做法:

// 使用 NodeContent 的 AddNode/RemoveNode 更新内容
nodeContent->AddNode(AceType::RawPtr(newNode), position);
nodeContent->RemoveNode(AceType::RawPtr(oldNode));

避免:

// ❌ 频繁分离/附加整个内容
slot->DetachNodeContent();
slot->AttachNodeContent(newContent); // 触发完整的挂载/卸载流程

ShallowBuilder 最佳实践

1. 延迟渲染时机

推荐做法:

void OnVisible() {
    if (!shallowBuilder_->IsExecuteDeepRenderDone()) {
        auto content = shallowBuilder_->ExecuteDeepRender();
        AddChild(content);
    }
}

避免:

// ❌ 过早执行深度渲染
void OnInit() {
    shallowBuilder_->ExecuteDeepRender(); // 可能不可见就创建了
}

2. 深度渲染函数设计

推荐做法:

// 深度渲染函数应该是幂等的
auto deepRenderFunc = [this]() -> RefPtr<UINode> {
    // 每次调用创建新节点
    return FrameNode::CreateFrameNode(...);
};

shallowBuilder_ = AceType::MakeRefPtr<ShallowBuilder>(std::move(deepRenderFunc));

避免:

// ❌ 深度渲染函数有副作用
auto deepRenderFunc = [this]() -> RefPtr<UINode> {
    globalCounter++; // 副作用
    return CreateNode();
};

常见问题

WithTheme 相关问题

Q1: 主题切换后子组件未更新?

问题现象:

auto withThemeNode = WithThemeNode::GetWithThemeNode(nodeId);
TokenThemeStorage::GetInstance()->StoreThemeScope(themeScopeId, newThemeId);
// 子组件主题未更新

原因: 未触发更新通知

解决方案:

// 存储新主题后,手动触发更新通知
TokenThemeStorage::GetInstance()->StoreThemeScope(themeScopeId, newThemeId);
auto withThemeNode = WithThemeNode::GetWithThemeNode(nodeId);
if (withThemeNode) {
    withThemeNode->NotifyThemeScopeUpdate(); // 必须调用
}

Q2: 嵌套 WithTheme 后主题获取错误?

问题现象:

WithTheme({ themeId: THEME_A }) {
  WithTheme({ themeId: THEME_B }) {
    // 子组件获取到 THEME_A 而非 THEME_B
  }
}

原因: 内层 SetThemeScopeId() 被跳过(因为 themeScopeId_ 不为 0)

解决方案:

// 确保使用 GetOrCreateWithThemeNode 创建新实例
auto innerWithTheme = WithThemeNode::GetOrCreateWithThemeNode(newNodeId);
// 新节点会自动设置新的 themeScopeId

Q3: 主题作用域销毁后仍被访问?

问题现象:

auto withThemeNode = WithThemeNode::GetWithThemeNode(nodeId);
withThemeNode->~WithThemeNode(); // 销毁
// 后续访问崩溃

原因: TokenThemeStorage 中仍保留引用

解决方案:

// 析构函数已自动处理
WithThemeNode::~WithThemeNode() {
    if (themeScopeDestroyCallback_) {
        themeScopeDestroyCallback_();
    }
    TokenThemeStorage::GetInstance()->RemoveThemeScope(GetId());
}

// 手动销毁时确保清理
withThemeNode->SetOnThemeScopeDestroy([]() {
    // 自定义清理逻辑
});

ContentSlot 相关问题

Q1: 内容附加后不显示?

问题现象:

slot->AttachNodeContent(content);
// content 的子节点不显示

原因: NodeContent 未添加子节点

解决方案:

// 先添加子节点到 NodeContent
content->AddNode(AceType::RawPtr(childNode1));
content->AddNode(AceType::RawPtr(childNode2));

// 再附加到 slot
slot->AttachNodeContent(content);

Q2: 内容切换时状态丢失?

问题现象:

slot->DetachNodeContent();
slot->AttachNodeContent(newContent); // 状态丢失

原因: DetachNodeContent() 会清空 slot 的子节点

解决方案:

// 方案 1: 保存 content 引用,重新附加
auto savedContent = content; // 保存引用
slot->DetachNodeContent();
slot->AttachNodeContent(AceType::RawPtr(savedContent)); // 状态保留

// 方案 2: 使用多个 content 实例
auto content1 = AceType::MakeRefPtr<NodeContent>();
auto content2 = AceType::MakeRefPtr<NodeContent>();
// 分别管理,切换时保留各自状态

Q3: OnAttachToMainTree 回调未触发?

问题现象:

content->SetAttachToMainTreeCallback([]() {
    LOGI("Attached"); // 未调用
});
slot->AttachNodeContent(content);

原因: slot 未挂载到主树

解决方案:

// 确保 slot 已在主树中
auto rootNode = GetRootNode();
rootNode->AddChild(slot); // 先挂载到主树

slot->AttachNodeContent(content); // 再附加内容,会触发回调

Q4: 内存泄漏 - 循环引用?

问题现象:

content->SetAttachToMainTreeCallback([content]() {
    // content 被回调持有,无法释放
});

原因: 回调持有 content 的强引用

解决方案:

// 使用弱引用
content->SetAttachToMainTreeCallback([weakContent = WeakClaim(content)]() {
    auto content = weakContent.Upgrade();
    if (content) {
        // 安全访问
    }
});

ShallowBuilder 相关问题

Q1: ExecuteDeepRender 多次调用导致重复创建?

问题现象:

if (needRender) {
    auto content = shallowBuilder_->ExecuteDeepRender();
    AddChild(content); // 重复添加
}

原因: 未检查 IsExecuteDeepRenderDone()

解决方案:

if (!shallowBuilder_->IsExecuteDeepRenderDone()) {
    auto content = shallowBuilder_->ExecuteDeepRender();
    AddChild(content);
}

Q2: 深度渲染函数中访问已释放对象?

问题现象:

auto deepRenderFunc = [rawPtr = this]() -> RefPtr<UINode> {
    return rawPtr->CreateNode(); // rawPtr 可能已释放
};

原因: 使用裸指针或弱引用未检查

解决方案:

// 使用弱引用并检查
auto deepRenderFunc = [weakThis = WeakClaim(this)]() -> RefPtr<UINode> {
    auto self = weakThis.Upgrade();
    if (!self) {
        return nullptr;
    }
    return self->CreateNode();
};

附录

A. 源码文件清单

WithTheme 组件:

  • 头文件: frameworks/core/components_ng/syntax/with_theme_node.h
  • 实现文件: frameworks/core/components_ng/syntax/with_theme_node.cpp
  • Modifier: frameworks/core/interfaces/native/implementation/with_theme_modifier.cpp
  • 测试文件: test/unittest/core/pattern/withtheme/withtheme_test_ng.cpp

ContentSlot 组件:

  • 头文件: frameworks/core/components_ng/syntax/content_slot_node.h
  • 实现文件: frameworks/core/components_ng/syntax/content_slot_node.cpp
  • Model: frameworks/core/components_ng/syntax/content_slot_model_ng.cpp
  • 测试文件: test/unittest/core/syntax/content_slot_syntax_test_ng.cpp

工具类:

  • NodeContent: frameworks/core/components_ng/syntax/node_content.h/cpp
  • SyntaxItem: frameworks/core/components_ng/syntax/for_each_item.h
  • ShallowBuilder: frameworks/core/components_ng/syntax/shallow_builder.h
  • NodeContentPeer: frameworks/core/interfaces/native/ani/node_content_peer.h

主题相关:

  • TokenThemeStorage: frameworks/core/components_ng/token_theme/token_theme_storage.h/cpp
  • TokenTheme: frameworks/core/components_ng/token_theme/token_theme.h

B. 关键常量定义

// inspector_constants.h
constexpr const char* JS_WITH_THEME_ETS_TAG = "WithTheme";
constexpr const char* JS_NODE_SLOT_ETS_TAG = "NodeSlot";
constexpr const char* JS_SYNTAX_ITEM_ETS_TAG = "SyntaxItem";

// token_theme_storage.h
constexpr int32_t SYSTEM_THEME_LIGHT_ID = -1;
constexpr int32_t SYSTEM_THEME_DARK_ID = -2;

C. 类型定义

// 主题作用域 ID
using TokenThemeScopeId = int32_t;

// 主题销毁回调
using ThemeScopeDestroyCallback = std::function<void()>;

// 深度渲染函数
using DeepRenderFunc = std::function<RefPtr<UINode>()>;

D. UINode 基类关键接口

// frameworks/core/components_ng/base/ui_node.h:112

// 主题相关
int32_t GetThemeScopeId() const;
virtual void SetThemeScopeId(int32_t themeScopeId);
virtual void UpdateThemeScopeId(int32_t themeScopeId);
virtual void UpdateThemeScopeUpdate(int32_t themeScopeId);
virtual void OnThemeScopeUpdate(int32_t themeScopeId);

// 节点类型
virtual bool IsAtomicNode() const = 0;
virtual bool IsSyntaxNode() const;

// 生命周期
virtual void OnAttachToMainTree(bool recursive);
virtual void OnDetachFromMainTree(bool recursive, PipelineContext* context = nullptr);

E. 参考资料

相关文档:

相关组件:

  • IfElse 组件: 条件渲染
  • ForEach 组件: 列表渲染
  • LazyForEach 组件: 懒加载列表渲染

设计模式:

  • 单例模式: WithThemeNode::GetOrCreateWithThemeNode()
  • 工厂模式: ContentSlotNode::GetOrCreateContentSlot()
  • 包装器模式: NodeContentPeer
  • 观察者模式: 主题更新通知机制

文档结束

本文档基于 OpenHarmony ace_engine 源码分析生成,如有疑问请参考源码实现。