15、鸿蒙Harmony Next开发:创建自定义组件

目录

自定义组件的基本用法

自定义组件的基本结构

struct

@Component

freezeWhenInactive

 build()函数

@Entry

EntryOptions

@Reusable

成员函数/变量

自定义组件的参数规定

build()函数

自定义组件生命周期

自定义组件的创建和渲染流程

自定义组件重新渲染

自定义组件的删除

自定义组件嵌套使用与示例


在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。进行UI界面开发时,不仅要组合使用系统组件,还需考虑代码的可复用性、业务逻辑与UI的分离,以及后续版本的演进等因素。因此,将UI和部分业务逻辑封装成自定义组件是不可或缺的能力。

自定义组件的特点:

  1. 可组合:允许开发者组合使用系统组件、及其属性和方法。
  2. 可重用:自定义组件可以被其他组件重用,并作为不同的实例在不同的父组件或容器中使用。
  3. 数据驱动UI更新:通过状态变量的改变,来驱动UI的刷新。

自定义组件的基本用法

@Component
struct HelloComponent {@State message: string = 'Hello, World!';build() {// HelloComponent自定义组件组合系统组件Row和TextRow() {Text(this.message).onClick(() => {// 状态变量message的改变驱动UI刷新,UI从'Hello, World!'刷新为'Hello, ArkUI!'this.message = 'Hello, ArkUI!';})}}
}

注意

如果在其他文件中引用自定义组件,需要使用export关键字导出组件,并在使用的页面import该自定义组件。

 可以在其他自定义组件的build()函数中多次创建HelloComponent,以实现自定义组件的重用。

@Entry
@Component
struct ParentComponent {build() {Column() {Text('ArkUI message')HelloComponent({ message: 'Hello World!' });Divider()HelloComponent({ message: '你好,世界!' });}}
}

自定义组件的基本结构

struct

自定义组件基于struct实现,struct + 自定义组件名 + {...}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。

说明

自定义组件名、类名、函数名不得与系统组件名重复。

@Component

@Component装饰器仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的能力,需要实现build方法描述UI,一个struct只能被一个@Component装饰。@Component可以接受一个可选的boolean类型参数。

说明

从API version 9开始,该装饰器支持在ArkTS卡片中使用。

从API version 11开始,@Component可以接受一个可选的boolean类型参数。

@Component
struct MyComponent {
}

freezeWhenInactive

组件冻结选项。

自定义组件冻结功能专为优化复杂UI页面的性能而设计,尤其适用于包含多个页面栈、长列表或宫格布局的场景。当状态变量绑定多个UI组件时,其变化易触发大量组件刷新,导致界面卡顿与响应延迟。为提升这类高负载UI界面的刷新性能,建议开发者使用自定义组件冻结功能。

组件冻结功能是一种性能优化机制,它会冻结非激活状态下的组件的刷新能力。当组件处于非激活状态时,即使其绑定的状态变量发生变化,也不会触发该组件的UI重新渲染,从而降低复杂UI场景下的刷新负载。

组件冻结的工作原理是:

  1. 开发者通过设置freezeWhenInactive属性,即可激活组件冻结机制。
  2. 启用后,系统将仅对处于激活状态的自定义组件进行更新,这使得UI框架可以尽量缩小更新范围,仅限于用户可见范围内(激活状态)的自定义组件,从而提高复杂UI场景下的刷新效率。
  3. 当之前处于inactive状态的自定义组件重新变为active状态时,状态管理框架会对其执行必要的刷新操作,确保UI的正确展示。

需要注意,组件active/inactive并不等同于其可见性。组件冻结目前仅适用于以下场景:

  1. 页面路由:当前栈顶页面为active状态,非栈顶不可见页面为inactive状态。
  2. TabContent:只有当前显示的TabContent中的自定义组件处于active状态,其余则为inactive。
  3. LazyForEach:仅当前显示的LazyForEach中的自定义组件为active状态,而缓存节点的组件则为inactive状态。
  4. Navigation:当前显示的NavDestination中的自定义组件为active状态,而其他未显示的NavDestination组件则为inactive状态。
  5. 组件复用:进入复用池的组件为inactive状态,从复用池上树的节点为active状态。
  6. 混用场景:对于以上场景的组合使用,例如TabContent下面使用LazyForEach,切换Tab时,API version 17及以下,LazyForEach中的所有节点都会被设置为active状态,而从API version 18开始,只有LazyForEach的屏上节点会被设置为active状态,其余则为inactive状态。
名称类型必填说明
freezeWhenInactiveboolean是否开启组件冻结。默认值false。true:开启组件冻结,false:不开启组件冻结。
@Component({ freezeWhenInactive: true })
struct MyComponent {
}

 build()函数

build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。

@Component
struct MyComponent {build() {}
}

@Entry

@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,仅允许存在一个由@Entry装饰的自定义组件作为页面的入口。@Entry可以接受一个可选的LocalStorage的参数。

说明

从API version 9开始,该装饰器支持在ArkTS卡片中使用。

从API version 10开始,@Entry可以接受一个可选的LocalStorage的参数或者一个可选的EntryOptions参数。

从API version 11开始,该装饰器支持在元服务中使用。

@Entry
@Component
struct MyComponent {
}

EntryOptions

命名路由跳转选项。

名称类型必填说明
routeNamestring表示作为命名路由页面的名字。
storageLocalStorage页面级的UI状态存储。
useSharedStorageboolean是否使用LocalStorage.getShared()接口返回的共享的LocalStorage实例对象。默认值false。true:使用共享的LocalStorage实例对象。false:不使用共享的LocalStorage实例对象。

说明

当useSharedStorage设置为true,并且storage也被赋值时,useSharedStorage的值优先级更高。

@Entry({ routeName : 'myPage' })
@Component
struct MyComponent {
}

@Reusable

@Reusable装饰的自定义组件具备可复用能力。详细请参考:@Reusable装饰器:组件复用。

说明

从API version 10开始,该装饰器支持在ArkTS卡片中使用。

@Reusable
@Component
struct MyComponent {
}

成员函数/变量

自定义组件除了必须要实现build()函数外,还可以实现其他成员函数,成员函数具有以下约束:

  • 自定义组件的成员函数为私有的,且不建议声明为静态函数。

自定义组件可以包含成员变量,成员变量具有以下约束:

  • 自定义组件的成员变量为私有的,且不建议声明成静态变量。
  • 自定义组件的成员变量本地初始化有些是可选的,有些是必选的。

自定义组件的参数规定

以上示例中,可以在build方法里创建自定义组件,同时在创建自定义组件的过程中,根据装饰器的规则来初始化自定义组件的参数。

@Component
struct MyComponent {private countDownFrom: number = 0;private color: Color = Color.Blue;build() {}
}@Entry
@Component
struct ParentComponent {private someColor: Color = Color.Pink;build() {Column() {// 创建MyComponent实例,并将创建MyComponent成员变量countDownFrom初始化为10,将成员变量color初始化为this.someColorMyComponent({ countDownFrom: 10, color: this.someColor })}}
}

以下示例代码将父组件中的函数传递给子组件,并在子组件中调用。

@Entry
@Component
struct Parent {@State cnt: number = 0submit: () => void = () => {this.cnt++;}build() {Column() {Text(`${this.cnt}`)Son({ submitArrow: this.submit })}}
}@Component
struct Son {submitArrow?: () => voidbuild() {Row() {Button('add').width(80).onClick(() => {if (this.submitArrow) {this.submitArrow()}})}.height(56)}
}

build()函数

所有在build()函数中声明的语句统称为UI描述,UI描述需要遵循以下规则:

  • @Entry装饰的自定义组件,其build()函数下的根节点唯一且必要,且必须为容器组件,其中ForEach禁止作为根节点
  • @Component装饰的自定义组件,其build()函数下的根节点唯一且必要,可以为非容器组件,其中ForEach禁止作为根节点。
    @Entry
    @Component
    struct MyComponent {build() {// 根节点唯一且必要,必须为容器组件Row() {ChildComponent() }}
    }@Component
    struct ChildComponent {build() {// 根节点唯一且必要,可为非容器组件Image('test.jpg')}
    }
    
  • 不允许声明本地变量,反例如下。
    build() {// 反例:不允许声明本地变量let num: number = 1;
    }
    
  • 不允许在UI描述里直接使用console.info,但允许在方法或者函数里使用,反例如下。
    build() {// 反例:不允许console.infoconsole.info('print debug log');
    }
    
  • 不允许创建本地的作用域,反例如下。
    build() {// 反例:不允许本地作用域{// ...}
    }
    
  •  不允许调用没有用@Builder装饰的方法,允许系统组件的参数是TS方法的返回值。
    @Component
    struct ParentComponent {doSomeCalculations() {}calcTextValue(): string {return 'Hello World';}@Builder doSomeRender() {Text(`Hello World`)}build() {Column() {// 反例:不能调用没有用@Builder装饰的方法this.doSomeCalculations();// 正例:可以调用this.doSomeRender();// 正例:参数可以为调用TS方法的返回值Text(this.calcTextValue())}}
    }
    
  • 不允许使用switch语法,当需要使用条件判断时,请使用if。示例如下。
    build() {Column() {// 反例:不允许使用switch语法switch (expression) {case 1:Text('...')break;case 2:Image('...')break;default:Text('...')break;}// 正例:使用ifif(expression == 1) {Text('...')} else if(expression == 2) {Image('...')} else {Text('...')}}
    }
    
  • 不允许使用表达式,请使用if组件,示例如下。
    build() {Column() {// 反例:不允许使用表达式(this.aVar > 10) ? Text('...') : Image('...')// 正例:使用if判断if(this.aVar > 10) {Text('...')} else {Image('...')}}
    }
    
  • 不允许直接改变状态变量,反例如下。
    @Component
    struct MyComponent {@State textColor: Color = Color.Yellow;@State columnColor: Color = Color.Green;@State count: number = 1;build() {Column() {// 应避免直接在Text组件内改变count的值Text(`${this.count++}`).width(50).height(50).fontColor(this.textColor).onClick(() => {this.columnColor = Color.Red;})Button("change textColor").onClick(() =>{this.textColor = Color.Pink;})}.backgroundColor(this.columnColor)}
    }
    

    在ArkUI状态管理中,状态驱动UI更新。

自定义组件生命周期

自定义组件生命周期,即用@Component或@ComponentV2装饰的自定义组件的生命周期,提供以下生命周期接口:

  • aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build函数之前执行。
  • onDidBuild:在组件首次渲染触发的build函数执行完成之后回调该接口,后续组件重新渲染将不回调该接口。开发者可以在这个阶段进行埋点数据上报等不影响实际UI的功能。不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现。
  • aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。

自定义组件生命周期流程如下图所示。

根据上面的流程图,接下来从自定义组件的初始创建、重新渲染和删除来详细说明。

自定义组件的创建和渲染流程

  1. 自定义组件的创建:自定义组件的实例由ArkUI框架创建。
  2. 初始化自定义组件的成员变量:通过本地默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序。
  3. 如果开发者定义了aboutToAppear,则执行该方法。
  4. 在首次渲染的时候,执行build方法渲染系统组件,如果子组件为自定义组件,则创建自定义组件的实例。在首次渲染的过程中,框架会记录状态变量和组件的映射关系,当状态变量改变时,驱动其相关的组件刷新。
  5. 如果开发者定义了onDidBuild,则执行该方法。

自定义组件重新渲染

当触发事件(比如点击)改变状态变量时,或者LocalStorage / AppStorage中的属性更改,并导致绑定的状态变量更改其值时:

  1. 框架观察到变化,启动重新渲染。
  2. 根据框架记录的状态变量和组件的映射关系,仅刷新发生变化的状态变量所关联的组件,实现最小化更新。

自定义组件的删除

例如if组件的分支改变或ForEach循环渲染中数组的个数改变,组件将被移除:

  1. 在删除组件之前,将调用其aboutToDisappear生命周期函数,标记着该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,前端节点已经没有引用时,将被Ark虚拟机垃圾回收。
  2. 自定义组件和它的变量将被删除,如果组件有同步的变量(如@Link、@Prop、@StorageLink),将从同步源上取消注册。

不建议在生命周期aboutToDisappear中使用async await。如果在此生命周期中使用异步操作(如 Promise 或回调方法),自定义组件将被保留在Promise的闭包中,直到回调方法执行完毕,这会阻止自定义组件的垃圾回收。

自定义组件嵌套使用与示例

通过以下示例,来详细说明自定义组件在嵌套使用时,自定义组件生命周期的调用时序:

// Index.ets
@Entry
@Component
struct Parent {@State showChild: boolean = true;@State btnColor: string = "#FF007DFF";// 组件生命周期aboutToAppear() {console.info('Parent aboutToAppear');}// 组件生命周期onDidBuild() {console.info('Parent onDidBuild');}// 组件生命周期aboutToDisappear() {console.info('Parent aboutToDisappear');}build() {Column() {// this.showChild为true,创建Child子组件,执行Child aboutToAppearif (this.showChild) {Child()}Button('delete Child').margin(20).backgroundColor(this.btnColor).onClick(() => {// 更改this.showChild为false,删除Child子组件,执行Child aboutToDisappear// 更改this.showChild为true,添加Child子组件,执行Child aboutToAppearthis.showChild = !this.showChild;})}}
}@Component
struct Child {@State title: string = 'Hello World';// 组件生命周期aboutToDisappear() {console.info('Child aboutToDisappear');}// 组件生命周期onDidBuild() {console.info('Child onDidBuild');}// 组件生命周期aboutToAppear() {console.info('Child aboutToAppear');}build() {Text(this.title).fontSize(50).margin(20).onClick(() => {this.title = 'Hello ArkUI';})}
}

以上示例中,Index页面包含两个自定义组件,一个是Parent,一个是Child,Parent及其子组件Child分别声明了各自的自定义组件生命周期函数(aboutToAppear / onDidBuild / aboutToDisappear)。

  • 应用冷启动的初始化流程为:Parent aboutToAppear --> Parent build --> Parent onDidBuild --> Child aboutToAppear --> Child build --> Child onDidBuild。此处体现了自定义组件懒展开特性,即Parent执行完onDidBuild之后才会执行Child组件的aboutToAppear。日志输出信息如下:

Parent aboutToAppear

Parent onDidBuild
Child aboutToAppear
Child onDidBuild

  • 点击Button按钮,更改showChild为false,删除Child组件,执行Child aboutToDisappear方法。
  • 如果直接退出应用,则会触发以下生命周期:Parent aboutToDisappear --> Child aboutToDisappear,此处体现了自定义组件删除顺序也是从父到子。日志输出信息如下:

Parent aboutToDisappear

Child aboutToDisappear

  • 最小化应用或者应用进入后台,当前Index页面未被销毁,所以并不会执行组件的aboutToDisappear。
  • 如果showChild的默认值为false,则应用冷启动的初始化流程为:Parent aboutToAppear --> Parent build --> Parent onDidBuild。日志输出信息如下:

Parent aboutToAppear
Parent onDidBuild

  • 如果showChild的默认值为false,直接退出应用,则只执行Parent aboutToDisappear方法。
  • 如果showChild的默认值为false,此时点击Button按钮,更改showChild为true,添加Child组件,添加流程为:Child aboutToAppear --> Child build --> Child onDidBuild。日志输出信息如下:

Child aboutToAppear
Child onDidBuild

当showchild为默认值true时,该示例的生命周期流程图如下所示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/89444.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/89444.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解Map.Entry.comparingByValue()和Map.Entry.comparingByKey()

文章目录深入理解Map.Entry.comparingByValue()和Map.Entry.comparingByKey()1. 方法定义comparingByKey()comparingByValue()2. 基本用法2.1 使用comparingByKey()2.2 使用comparingByValue()3. 方法重载版本comparingByKey(Comparator)comparingByValue(Comparator)4. 高级用…

Mac下载mysql

安装 brew list --versions | grep mysql查看已安装的mysql版本brew search mysql查看支持的mysql版本brew info mysql查看mysql版本信息brew install mysql进行安装/opt/homebrew/opt/mysql/bin/mysqld --initialize-insecure --user$(whoami) --basedir$(brew --prefix mysql…

PageHelper使用说明文档

文章目录一、简介二、集成步骤三、使用方法四、注意事项五、高级用法一、简介 PageHelper 是一个开源的 MyBatis 分页插件,它可以帮助我们在使用 MyBatis 进行数据库操作时方便地实现分页功能。通过简单的配置和少量的代码修改,就可以在查询数据时实现分…

grpo nl2sql qwen3 模型强化学习训练有效果的成立条件有哪些

在使用GRPO(强化学习算法)对Qwen3模型在NL2SQL(自然语言到SQL转换)任务上进行强化学习(RL)训练时,其效果成立的核心条件可归纳为以下几个关键维度,这些条件相互关联,共同…

面向向量检索的教育QA建模:九段日本文化研究所日本语学院的Prompt策略分析(6 / 500)

面向向量检索的教育QA建模:九段日本文化研究所日本语学院的Prompt策略分析(6 / 500) 系列说明 500 所日本语言学校结构化建模实战,第 6 篇。每篇拆解 1 所学校在 Prompt-QA 系统中的建模策略,分享工程经验,…

墨刀原型图的原理、与UI设计图的区别及转换方法详解-卓伊凡|贝贝

墨刀原型图的原理、与UI设计图的区别及转换方法详解-卓伊凡|贝贝最近有个设计由于时间比较仓促直接用 原型做的,但是原型做的大家都知道是没法用的,以下讲解原型和ui的区别,其次我们下面有三种方法把墨刀的原型变成UI图。一、墨刀原型图的原理…

前端 nodejs vue2 开发环境和微信开发环境 故障终极处理

现象某个vue2旧项目 引入vue-ls 组件等组件,冲突失败后删除,导致开发环境 vxe-table加载失败,还原后还是不行。前段项目崩溃。报警sass 某个方法 Deprecated ,之前不会处理方式_失败回退代码项目代码 删除 node_modules, 删除 …

【后端】.NET Core API框架搭建(9) --配置使用Log4Net日志

目录 1.添加包 2.新建公用类 3.新建配置 4.注册 4.1.类库项目设置 5.使用 在 .NET Core 项目中使用 Log4Net 做日志记录,具有很多优势。尽管 .NET Core 自带了 ILogger 接口(如使用内置的 ConsoleLogger、DebugLogger 等),但…

Agent交互细节

本文参考了https://www.bilibili.com/video/BV1v9V5zSEHA/视频及原作者代码实践 本文主要实践在第3节1、MCP MCP官方地址:https://modelcontextprotocol.io/introduction MCP 是一个开放协议,它规范了应用程序向 LLM 提供上下文的方式。 架构&#xff1a…

AI+医疗!VR和MR解剖学和针灸平台,智能时代如何重塑健康未来

在智能时代,“AI医疗”正从精准诊断入手,推动医疗系统变革,通过个性化健康管理、智能诊疗辅助等方式重塑健康未来!将人工智能(AI)与虚拟实境(VR)应用到中医教学,透过该系…

Sersync和Rsync部署

学习参考连接 以下是我在学习过程中借鉴的经验和下载资源链接,感谢几位大佬的帮助,也供各位参考。 Rsync踩坑: https://blog.csdn.net/XiaoXiaoYunXing/article/details/120160395 Sersync下载源 http://down.whsir.com/downloads/sersy…

Django基础(四)———模板常用过滤器

前言上篇文章给大家介绍了DTL模板的部分知识点这篇文章继续带大家深入理解Django框架中的模板过滤器一、模板常用过滤器1.add将传进来的参数添加到原来的值上面。这个过滤器会尝试将值和 参数转换成整形然后进行相加。如果转换成整形过程中失败了,那么会将值和参数进…

国内MCP服务器搜索引擎有哪些?MCP导航站平台推荐

在人工智能技术蓬勃发展的今天&#xff0c;AI模型与外部工具和服务的交互能力正成为推动技术进步的关键。AIbase&#xff08;<https://mcp.aibase.cn/>&#xff09;作为一个专注于MCP(Model Context Protocol&#xff0c;模型上下文协议)服务器的集合平台&#xff0c;为全…

Python中with的作用和用法

在这里我们来详细解释一下Python中非常重要的 with 语句。 我会从 “为什么需要它” 开始&#xff0c;然后讲解 “它是什么以及如何使用”&#xff0c;最后深入到 “它的工作原理” 和 “如何自定义”。1. 为什么需要 with 语句&#xff1f;(The Problem) 在编程中&#xff0c;…

缓存雪崩、缓存穿透,缓存击穿

Redis是一个完全开源免费的高性能非关系型&#xff08;NOSQL&#xff09;的key-value数据库。 Redis不可能把所有的数据都缓存起来(内存昂贵且有限)&#xff0c;所以Redis需要对数据 设置过期时间&#xff0c;并采用的是惰性删除定期删除两种策略对过期键删除。Redis对过期键的…

springmvc跨域解决方案

在Spring MVC中处理跨域请求&#xff08;CORS&#xff0c;Cross-Origin Resource Sharing&#xff09;通常涉及到配置HTTP响应头&#xff0c;以允许来自不同源的请求。Spring MVC提供了多种方式来配置CORS&#xff0c;包括全局配置和局部配置。 使用CrossOrigin注解 在控制器的…

btstack移植之安全配对(二)

3.13.3 Legacy配对首先&#xff0c;我们回复的paring response中&#xff0c;可以看到我们不支持secure connection&#xff0c;所以我们走的是legacy配对模式。图3-74 secure连接不支持然后&#xff0c;master在pairing confirm包中回复了confirm value。图3-75 master发送con…

FRP配置( CentOS 7 上安装 FRP教程 )

** 如果你们公司没有公网IP&#xff0c;但是又想实现内网穿透&#xff0c;远程调用接口&#xff0c;在家也能调用公司服务器&#xff0c;但是nkg ssl有问题&#xff0c;花生壳坑壁&#xff0c;那么FRP是你最佳的选择&#xff01;&#xff01;&#xff01;** 不过有个前提&#…

第三次mysql作业

建立库建立mydb11_syu库2.创建s表&#xff0c;创建sc表二&#xff0e;插入数据向s表插入数据2.向sc表插入数据三&#xff0e;查询1.分别查询student表和score表的所有记录2.查询student表的第2条到5条记录3.从student表中查询计算机系和英语系的学生的信息4.从student表中查询年…

不同场景下git指令的搭配

添加账号 git config --global user.name "YourName" git config --global user.email "your_emailexample.com"设置 Git 默认分支名称为 main&#xff1a; git config --global init.defaultBranch main初始化仓库&#xff1a; git init配置SSH 密钥 如果…