@Builder装饰器-自定义构建函数
前面介绍了如何创建一个自定义组件。该自定义组件内部UI结构固定,仅与使方法进行数据传递。ArkUI还提供了一种更轻量的UI 元素复用机制@Builder,@Builder
所装饰的函数遵循build( )函数语法规则,开发者可以将重复使用的UI 元素抽象成一个方法,在 build 方法里调用。 为了简化语言,我们将@Builder 装饰的函数也称为"自定义构建函数"。从API version 9开始,该装饰器支持在ArkTS卡片中使用。
装饰器使用说明:
语法的定义
TypeScript
@Builder MyBuilderFunction( ){...}
使用方法
TypeScript
this.MyBuilderFunction(){...}
-
允许在自定义组件内定义一个或多个@Builder 方法,该方法被认为是该组件的私有、特殊类型的成员函数。
-
自定义构建函数可以在所属组件的build 方法和其他自定义构建函数中调用,但不允许在组件外调用。
-
在自定义函数体中,this指代当前所属组件,组件的状态变量可以在自定义构建函数内访问。建议通过this访问自定义组件的状态变量而不是参数传递
全局自定义构建函数
语法的定义
TypeScript
@BUilder function MyGlobalBuilderFunction(){...}
使用方法
TypeScript
MyGlobalBuilderFunction()
-
全局的自定义构建函数可以被整个应用获取,不允许使用this和bind方法。
-
如果不涉及组件状态变化,建议使用全局的自定义构建方法
参数传递规则
自定义构建函数的参数传递有按值传递和按引用传递两种,均需遵守以下规则:
参数的类型必须与参数声明的类型一致,不允许undefined、nu1l和返回undefined、null的表达式
引用传参(最常用)
TypeScript
//自定义一个构建函数
//引用传参使用$$符号
@Builder
function MyBulilder($$:{username:string}){
//用的时候也要使用$$
Column(){
Text(`hello ${$$.username}`)
.fontSize(40)
.margin(20)
}
}
@Entry
@Component
struct Parent{
@State person_name: string='张三'
build(){
Column(){
Divider()
MyBulilder({username: this.person_name})
Button('改变值').onClick(()=>{
this.person_name ='李四'
})
}
}
}
值传参(了解 并不实用)
TypeScript
//自定义一个构建函数
//按值传参
@Builder
function MyBulilder(username:string){
Column(){
Text(`hello ${username}`)
.fontSize(40)
.margin(20)
}
}
@Entry
@Component
struct Parent{
@State person_name: string='张三'
build(){
Column(){
Divider()
MyBulilder(this.person_name)
Button('改变值').onClick(()=>{
this.person_name ='李四'
})
}
}
}
状态管理
状态管理概述
在前文的描述中,我们构建的页面多为静态界面。如果希望构建一个动态的、有交互的界面,就需要引入"状态"的概念
在本章节开始的案例中,用户与应用程序的交互触发了文本状态变更,状态变更引起了UI 渲染,UI 从"Hello world"变更为"Hello rkUI",这个过程就用到了状态。
在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个U模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制。
自定义组件拥有变量,变量必须被装饰器装饰才可以成为状态变量,状态变量的改变会引起UI的渲染刷新。如果不使用状态变量,UI只能在初始化时渲染,后续将不会再刷新。下图展示了state和iew(UI)之间的关系
-
View(UI):UI 渲染,指将build 方法内的 UI 描述和@Builder 装饰的方法内的UI 描述映射到界面。
-
state:状态,指驱动U更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染。
基本概念
-
状态变量: 被状态装饰器装饰的变量,状态变量值的改变会引起UI的染更新。示例:@State num: number=1,其中,@State是状态装饰器,num是状态变量。
-
**常规变量:**没有被状态装饰器装饰的变量,通常应用于辅助计算。它的改变永远不会引起UI的刷新。以下示例中increaseBy 变量为常规变量。
-
数据源/同步源:状态变量的原始来源,可以同步给不同的状态数据。通常意义为父组件传给子组件的数据。以下示例中数据源为count:1。
-
命名参数机制:父组件通过指定参数传递给子组件的状态变量,为父子传递同步参数的主要手段。示例:CompA:({aPrp:this.aProp})。
-
**从父组件初始化:**父组件使用命名参数机制,将指定参数传递给子组件。子组件初始化的默认值在有父组件传值的情况下,会被覆盖。
-
**初始化子节点:**父组件中状态变量可以传递给子组件,初始化子组件对应的状态变量。
-
本地初始化:在变量声明的时候赋值,作为变量的默认值。示例:@statecount:number =0
管理组件拥有的状态
@State装饰器-组件内状态
@state 装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。
在状态变量相关装饰器中,@state是最基础的,使变量拥有状态属性的装饰器,它也是大部分状态变量的数据源。从Pversion9开始,该装饰器支持在ArkTs 卡片中使用。
概述
@State装饰的变量,与声明式范式中的其他被装价变量一样,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化。初始化也可选择使用命名参数机制从父组件完成初始化。
装饰器使用说明
使用场景
装饰简单类型的变量 以下示例为@state装饰的简单类型,count被@state装饰成为状态变量,count的改变引起Button组件的刷新:
-
当状态变量count改变时,查询到只有Button组件关联了它;
-
执行Button组件的更新方法,实现按需刷新。
示例
点击修改姓名将会在张三,李四之间进行切换
点击修改年龄将会按照你设定的规则进行年龄的加减
当子组件上传参数的时候将使用子组件的参数
当没有的时候默认是父组件的数据
@Prop装饰器-父子单项同步
@Prop 装饰的变量可以和父组性建立单向的同步关系。@Prop装饰的变量是可变但是变化不会同步回其父组件。从API version 9开始,该装饰器支持在rkTS 卡中使用。
概述
-
@Prop 装饰的变量和父组件建立单向的同步关系:
-
@Prop变量允许在本地修改,但修改后的变化不会同步回父组件
-
当父组件中的数据源更改时,与之相关的@Prop 装饰的变量都会自动更新。如果子组件已经在本地修改了@Prop 装饰的相关变量值,而在父组件中对应的@state装饰的变量被修改后,子组件本地修改的@Prop装饰的相关变量值将被覆盖
限制条件
-
@Prop 修饰复杂类型时是深拷贝,在拷贝的过程中除了基本类型、Map、Set、Date、Array 外,都会丢失类型。
-
@Prop装饰器不能在@Entry装饰的自定义组件中使用
装饰器使用规则说明
使用场景:
父组件@State 到子组件@Prop简单数据类型同步
以下示例是@state 到子组件@Prop 简单数据同步,父组件ParentComponent的状态变量 countDownstartValue 初始化子组件 CountDowncomponent 中eprop 装饰的 count,点击"Try again",,count 的修改仅保留在 CountDownComponent不会同步给父组件ParentComponent。
ParentComponent的状态变量countDownstartValue 的变化将重冒CountDownComponent的count。
代码:
TypeScript
@Component
struct MyChid{
@Prop
age: number =33
private increase: number=1 //私有
build(){
Column(){
if (this.age>=18){
Text(`已经age成年了:${this.age}`).height(80)
}else
{
Text(`age未成年了:${this.age}`).height(80)
}
Button('-修改子组件age').onClick(()=>{
this.age-=this.increase
})
.height(80)
.width(250)
.margin(5)
}
}
}
@Entry
@Component
struct Myparent {
@State
init_age: number =16
build(){
Column(){
Text(`父组件初始值:${this.init_age}`).height(80)
Button('+修改父组件age').onClick(()=>{
this.init_age+=1
})
.height(80)
.margin(5)
.width(250)
Divider()
MyChid({age:this.init_age,increase:2})
}
}
}
@Link装饰器-父子双向同步
子组件中被@Link 装饰的变量与其父组件中对应的数据源建立双向数据绑定。从APIversion 9开始,该装饰器支持在ArkTS卡片中使用。
需要注意:@Link 装饰的变量与其父组件中的数据源共享相同的值。@Link 装饰器不能在@Entry 装饰的自定义组件中使用。
代码示例展示:
TypeScript
//自定义按钮的信息类型
class ButtonState{
value:string;
width:number=0;
constructor(value:string, width:number){
this.value = value;
this.width = width;
}
}
@Component
struct MyChildGreenButton{
//拥有 绿色按钮的组件,Link装饰器 实现双向同步
@Link
buttonState:ButtonState //自定义对象类型
build(){
Button(`${this.buttonState.value}`)
.width(this.buttonState.width)
.height(150)
.backgroundColor(Color.Green)
.onClick(()=>{
//点击按钮 实现宽度的变化
if(this.buttonState.width<700){
this.buttonState.width+=100
}else {
//按钮宽度回到初始值
this.buttonState = new ButtonState('绿色按钮',100)
}
})
}
}
@Component
struct MyChildRedButton{
//拥有 红色按钮的组件,Link装饰器 实现双向同步
@Link
value: string
@Link
buttomWidth: number;
build(){
Button(`${this.value}`)
.width(this.buttomWidth)
.height(150)
.backgroundColor(Color.Red)
.onClick(()=>{
//点击按钮 实现宽度的变化
if(this.buttomWidth<700){
this.buttomWidth+=100
}else {
//按钮宽度回到初始值
this.buttomWidth = 100
}
})
}
}
@Entry
@Component
struct MYParent{
@State parentGreenButton:ButtonState=new ButtonState('一号',100) //状态变量
@State parentRedValue: string = '二号子组件' //状态变量
@State parentRedWidth: number = 200 //状态变量 //绿色按钮的宽度
build(){
Column(){
//父组件中调整按钮宽度
Button(`父组件中修改绿色按钮的宽度:${this.parentGreenButton.width}`)
.onClick(()=>{
this.parentGreenButton.width = this.parentGreenButton.width < 700 ? this.parentGreenButton.width+100:100
})
Button(`父组件中修改红色按钮的宽度:${this.parentRedWidth}`)
.onClick(()=>{
this.parentRedWidth = this.parentRedWidth < 700 ? this.parentRedWidth+100:100
})
Divider()
MyChildGreenButton({buttonState:$parentGreenButton}) //传递Link装饰器的变量时候加 $ 符号
MyChildRedButton({value:$parentRedValue,buttomWidth:$parentRedWidth})
}
}
}
if/else 条件渲染
ArkTS 提供了渲染控制的能力。条件渲染可根据应用的不同状态,使用if、else 和else if 渲染对应状态下的UI内容。从API version 9开始,该接口支持在 ArkTS卡片中使用。
使用规则
-
支持if、else和else if 语句。
-
if、else if 后跟随的条件语句可以使用状态变量
-
允许在容器组件内使用,通过条件渲染语句构建不同的子组件。
-
条件渲染语句在涉及到组件的父子关系时是"透明"的,当父组件和子组件之间存在一个或多个if语句时,必须遵守父组件关于子组件使用的规则。
-
"每个分支内部的构建函数必须遵循构建函数的规则,并创建一个或多个组件。无法创建组件的空构建函数会产生语法错误。
-
某些容器组件限制子组件的类型或数量,将条件渲染语句用于这些组件内时,这些限制将同样应用于条件渲染语句内创建的组件。例如,Grid容器组件的子组件仅支持GridItem 组件,在Grid 内使用条件渲染语句时,条件渲染语句内仅允许使用GridItem组件
更新机制
当if、else if 后跟随的状态判断中使用的状态变量值变化时,条件渲染语句会进行更新,更新步骤如下:
-
评估if和else if的状态判断条件,如果分支没有变化,请无需执行以下步骤。如果分支有变化,则执行2、3步骤:
-
删除此前构建的所有子组件。
-
执行新分支的构造函数,将获取到的组件添加到if 父容器中。如果缺少适用的 else分支,则不构建任何内容。
条件可以包括 Typescript 表达式。对于构造函数中的表达式,此类表达式不得更改应用程序状态。
使用if进行条件渲染
if 语句的每个分支都包含一个构建函数。此类构建函数必须创建一个或多个子组件。在初始渲染时,if语句会执行构建函数,并将生成的子组件添加到其父组件中。
"每当if或else if条件语句中使用的状态变量发生变化时,条件语句都会更新并重新评估新的条件值。如果条件值评估发生了变化,这意味着需要构建另一个条件分支。此时ArkUI 框架将:
-
删除所有以前渲染的(早期分支的)组件。
-
执行新分支的构造函数,将生成的子组件添加到其父组件中
代码演示
分为两种 :第一种 点击"是否真假的按钮的时候" 计数器的值会归零
TypeScript
//定义子组件
@Component
struct MyChild{
//计数器
@State counter:number=0
label: string;
build(){
Row(){
Text(`${this.label}`)
.width(100)
.height(100)
.fontSize(20)
Button(`计数器的值:${this.counter}`)
.width(200)
.height(60)
.onClick(()=>{
this.counter += 1;
})
}
}
}
@Entry
@Component
struct MyParent{
@State flag:boolean=false;
build(){
Column(){
//根据判断决定子组件
if(this.flag){
MyChild({label:'zhenzhen'})
}else {
MyChild({label:'jiajia'})
}
Divider()
Button(`是否真假:${this.flag}`)
.width(300)
.height(60)
.fontSize(30)
.margin(40)
.onClick(()=>{
this.flag=!this.flag
})
}
}
}
第二种 点击是否真假的时候计数器的值不会进行更新归零
TypeScript
//定义子组件
@Component
struct MyChild{
//计数器
@Link counter:number;
label: string;
build(){
Row(){
Text(`${this.label}`)
.width(100)
.height(100)
.fontSize(20)
Button(`计数器的值:${this.counter}`)
.width(200)
.height(60)
.onClick(()=>{
this.counter += 1;
})
}
}
}
@Entry
@Component
struct MyParent{
@State flag:boolean=false;
@State parentCount: number=0
build(){
Column(){
//根据判断决定子组件
if(this.flag){
MyChild({counter:$parentCount,label:'zhenzhen'})
}else {
MyChild({counter:$parentCount,label:'jiajia'})
}
Divider()
Button(`是否真假:${this.flag}`)
.width(300)
.height(60)
.fontSize(30)
.margin(40)
.onClick(()=>{
this.flag=!this.flag
})
}
}
}