鸿蒙最佳实践之组件动态创建
前言
大家好。我是青蓝逐码组织的君莫笑,今天跟大家分享一下组件动态创建知识的解析。
组件动态创建解决了什么问题?
为了解决组件加载缓慢的问题,即加速组件的渲染和创建
什么原理?
官网中指出:ArkUI框架提供的UI动态操作支持组件的预创建。组件预创建可以满足开发者在非build生命周期中进行组件创建,创建后的组件可以进行属性设置、布局计算等操作。之后在页面加载时进行使用,可以极大提升页面响应速度。
由下图可知,组件预创建机制,可以利用动画执行过程空闲时间进行组件预创建和属性设置。在动画结束后,再进行属性和布局的更新,节省了组件创建的时间,从而加快了页面渲染。

组件动态创建就是使用FrameNode自定义节点
使用FrameNode自定义节点有哪些性能优势呢?
- 减少自定义组件创建开销 :在采用声明式开发范式中,若使用ArkUI的自定义组件对节点树中的每个节点进行定义,往往会遇到节点创建效率低下的问题。 这主要是因为每个节点在ArkTS引擎中都需要分配内存空间来存储应用程序的自定义组件和状态变量。在节点创建过程中,还必须执行组件ID、组件闭包以及状态变量之间的依赖关系收集等操作。 相比之下,使用ArkUI的FrameNode,可以避免创建自定义组件对象和状态变量对象,无需进行依赖收集,从而显著提升组件创建的速度。
- 组件更新更快 :动态布局类框架的更新场景中,通常存在一个由树形数据结构ViewModelA创建的UI组件树TreeA。当需要使用新的数据结构ViewModelB来更新TreeA时,尽管声明式开发范式可以实现数据驱动的自动更新,但这一过程中却伴随着大量的diff操作,如下图所示。对于ArkTS引擎而言,在对一个复杂组件树(深度超过30层,包含100至200个组件)执行diff算法时,几乎无法在120Hz的刷新率下保持满帧运行。然而,使用ArkUI的FrameNode扩展,框架能够自主掌控更新流程,实现高效的按需剪枝。特别是针对那些仅服务于少数特定业务的动态布局框架,利用这一扩展,可以实现快速的更新操作。
- 能够直接操作组件树:使用声明式开发范式还存在组件树结构更新操作困难的痛点,比如将组件树中的一个子树从当前子节点完整移到另一个子节点,使用声明式开发范式无法直接调整组件实例的结构关系,只能通过重新渲染整棵组件树的方式实现上述操作。而使用ArkUI的FrameNode扩展,则可以通过操作FrameNode来很方便的操控该子树,将其移植到另一个节点,这样只会进行局部渲染刷新,性能更优。

开始操作
- 创建自定义节点
scss
// 入参类型
class Params {
text: string
img: ResourceStr
constructor(text: string, img: ResourceStr) {
this.text = text;
this.img = img;
}
}
// UI组件
@Builder
function buildText(params: Params) {
Column() {
Text(params.text)
.fontSize(20)
Image(params.img)
.width(30)
}
}
- 实现NodeController类
scala
class TextNodeController extends NodeController {
private textNode: BuilderNode<[Params]> | null = null;
constructor() {
super();
}
makeNode(context: UIContext): FrameNode | null {
this.textNode = new BuilderNode(context);
this.textNode.build(wrapBuilder<[Params]>(buildText), new Params('你好', $r('app.media.startIcon')));
return this.textNode.getFrameNode();
}
}
说明:使用BuilderNode的build方法,构建组件树。方法build()需要传入两个参数,第一个参数为通过wrapBuilder()封装的全局@Builder方法。第二个参数为对应的@Builder方法所需的参数对象。若@Builder方法不带参数或者存在默认参数,则build()的第二个参数可以不设置。
- 显示自定义节点
less
@Entry
@Component
struct Index {
textNodeController: TextNodeController = new TextNodeController()
build() {
Column() {
NodeContainer(this.textNodeController)
}
}
}
效果:
4. 动态删除组件
通过条件控制语句可以将NodeContainer节点进行移除或者显示
kotlin
Column() {
if (this.isShow) {
NodeContainer(this.textNodeController)
}
Button('点我')
.onClick(() => {
this.isShow = !this.isShow
})
}
- 动态更新组件
ini
replaceBuilderNode(newNode: BuilderNode<Object[]>) {
this.textNode = newNode;
this.rebuild();
}
typescript
@Entry
@Component
struct Index {
textNodeController: TextNodeController = new TextNodeController()
@State isShow: boolean = true
buildNewNode(): BuilderNode<[Params]> {
let uiContext: UIContext = this.getUIContext();
let textNode = new BuilderNode<[Params]>(uiContext);
textNode.build(wrapBuilder<[Params]>(buildText), new Params('创建新的节点', $r('app.media.app_background')))
return textNode;
}
build() {
Column() {
NodeContainer(this.textNodeController)
Button('点我')
.onClick(() => {
this.textNodeController.replaceBuilderNode(this.buildNewNode());
})
}
}
}
说明:点击后会出现图片闪动,因为为重新删除节点并且再添加节点,如果为update()更新自定义节点即不会出现图片闪动的影响。
demo:
javascript
update() {
if (this.textNode !== null) {
this.textNode.update(new Params('更新一下', $r('app.media.background')));
}
}
typescript
@Entry
@Component
struct Index {
textNodeController: TextNodeController = new TextNodeController()
@State isShow: boolean = true
buildNewNode(): BuilderNode<[Params]> {
let uiContext: UIContext = this.getUIContext();
let textNode = new BuilderNode<[Params]>(uiContext);
textNode.build(wrapBuilder<[Params]>(buildText), new Params('创建新的节点', $r('app.media.app_background')))
return textNode;
}
build() {
Column() {
NodeContainer(this.textNodeController)
Button('点我创建新的节点')
.onClick(() => {
this.textNodeController.replaceBuilderNode(this.buildNewNode());
})
Button('点我更新节点')
.onClick(() => {
this.textNodeController.update()
})
}
}
}
最后
这篇文章为简单探索一下组件动态创建,适合创建一些简单的组件,更加进阶以及复杂的布局,在后面的文章中会进行解析和探索,想自行探索的同学可以查看华为官方文档
如果你兴趣想要了解更多的鸿蒙应用开发细节和最新资讯 ,甚至你想要做出一款属于自己的应用!欢迎在评论区留言或者私信或者看我个人信息,可以加入技术交流群。