ArkUI 特殊语法组件指南
文档版本: v1.0 更新时间: 2026-02-05 源码版本: OpenHarmony ace_engine (master 分支) 作者: 基于 CLAUDE.md 规范自动生成
目录
概述
组件体系
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());
}
更新机制:
- 调用
NotifyThemeScopeUpdate()触发主题更新 - 向父节点的
UpdateThemeScopeUpdate()传播更新通知 - 子树中所有监听主题变化的节点收到通知
主题存储管理
销毁时的资源清理 (with_theme_node.cpp:23-29):
WithThemeNode::~WithThemeNode()
{
// 1. 调用销毁回调
if (themeScopeDestroyCallback_) {
themeScopeDestroyCallback_();
}
// 2. 从 TokenThemeStorage 移除主题作用域
TokenThemeStorage::GetInstance()->RemoveThemeScope(GetId());
}
资源管理流程:
- 执行用户注册的销毁回调
- 从全局主题存储中移除当前作用域
- 释放主题相关资源
主题作用域隔离
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_;
};
隔离原理:
- 作用域隔离: 每个
WithThemeNode有唯一的themeScopeId - 主题映射:
TokenThemeStorage维护themeScopeId → themeId映射 - 主题查找: 子节点通过
GetTheme(themeScopeId)获取对应主题 - 默认主题: 未指定作用域的节点使用默认主题
隔离效果示例
// 全局主题: 蓝色系
@Entry
@Component
struct MainPage {
build() {
Column() {
Text("默认主题 - 蓝色")
// 主题作用域 A: 红色系
WithTheme({ themeId: THEME_RED }) {
Text("主题 A - 红色")
// 嵌套主题作用域 B: 绿色系
WithTheme({ themeId: THEME_GREEN }) {
Text("主题 B - 绿色")
}
Text("回到主题 A - 红色")
}
Text("回到默认主题 - 蓝色")
}
}
}
主题查找链:
- 子节点首先查找当前
themeScopeId对应的主题 - 如果未找到,向上查找父作用域的主题
- 最终使用默认主题
销毁回调机制
回调注册
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();
}
更新传播:
- 调用
NotifyThemeScopeUpdate() - 向父节点传播
UpdateThemeScopeUpdate() - 父节点递归通知子树
- 子节点重新获取主题值并更新 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);
}
附加流程:
- 冲突检查: 如果当前
content_被其他 slot 持有,先分离 - 引用更新: 更新
content_为新内容 - 双向绑定: 调用
content->AttachToNode(this)建立双向关联
DetachNodeContent 实现
源码 (content_slot_node.h:60-66):
void DetachNodeContent()
{
if (content_) {
content_->DetachFromNode();
}
content_ = nullptr;
}
分离流程:
- 内容分离: 调用
content_->DetachFromNode() - 引用清除: 将
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;
}
设计模式:
- 单例模式: 相同
nodeId的ContentSlotNode全局唯一 - 懒加载: 首次调用时创建,后续复用
- 注册机制: 创建后注册到
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();
}
}
附加关键步骤:
- 冲突处理: 如果已关联其他 slot,先分离
- 引用建立: 建立到 slot 的弱引用
- 子节点迁移: 将所有
children_添加到 slot - Builder 更新: 更新 Builder 节点的父子关系
- 脏标记: 标记需要刷新
- 回调触发: 如果 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();
}
}
}
分离关键步骤:
- 引用清除: 重置
nodeSlot_ - 子节点保存: 保存 slot 的子节点到
children_ - Slot 清空: 调用
slot->Clean()清空 slot - Builder 更新: 移除 Builder 节点关系
- 回调触发: 如果 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();
}
}
挂载流程:
- 调用父类
UINode::OnAttachToMainTree() - 通知
content_触发OnAttachToMainTree() 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();
}
}
卸载流程:
- 调用父类
UINode::OnDetachFromMainTree() - 通知
content_触发OnDetachFromMainTree() 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);
使用场景:
- 动态内容切换: 实现类似 Tab 切换的内容管理
- 懒加载: 按需加载和卸载内容
- 内容复用: 同一份内容在不同位置显示
- 生命周期管理: 精确控制内容的挂载/卸载时机
数据成员:
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(): 返回trueIsAtomicNode(): 返回false(支持子节点)- 析构时设置
SetRemoveSilently(true)
使用场景:
- ForEach 列表项: 包装列表中的每个渲染项
- LazyForEach 懒加载项: 包装懒加载的渲染项
- IfElse 分支项: 包装条件分支的内容
- 节点复用: 通过 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执行时不会创建仅保存创建方法。
使用场景:
- ListItem 懒加载: 列表项滚动到可视区域时才创建内容
- GridItem 懒加载: 网格项按需创建
- 性能优化: 减少初始渲染负担
- 最小化更新: 仅在需要时执行深度渲染
执行流程:
- 浅层构建: 创建
ShallowBuilder并保存deepRenderFunc_ - 延迟执行: 调用
ExecuteDeepRender()时才真正创建节点 - 状态标记:
executeDeepRenderDone_标记是否已执行 - 节点返回: 返回创建的
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++ 实现之间转换
使用场景:
- C API 桥接: 为 C API 提供 NodeContent 访问能力
- 跨语言调用: ArkTS Native 与 C++ 层交互
- 句柄管理: 通过句柄操作 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()
}
}
}
}
实现原理:
- 每个
WithTheme创建独立作用域 - 子组件从最近的作用域获取主题
- 主题切换时调用
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));
状态保留原理:
NodeContent持有子节点引用- 分离时不销毁子节点,仅从 slot 移除
- 重新附加时恢复之前的节点
- 节点状态自然保留
场景 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. 参考资料
相关文档:
- Conditional_Rendering_Guide.md - 条件渲染组件指南
- Syntax_Architecture_Overview.md - 语法架构总览
- CLAUDE.md - 项目开发指南
相关组件:
- IfElse 组件: 条件渲染
- ForEach 组件: 列表渲染
- LazyForEach 组件: 懒加载列表渲染
设计模式:
- 单例模式:
WithThemeNode::GetOrCreateWithThemeNode() - 工厂模式:
ContentSlotNode::GetOrCreateContentSlot() - 包装器模式:
NodeContentPeer - 观察者模式: 主题更新通知机制
文档结束
本文档基于 OpenHarmony ace_engine 源码分析生成,如有疑问请参考源码实现。