装饰器,状态管理和if判断(HarmonyOS学习第六课)

@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组件的刷新:

  1. 当状态变量count改变时,查询到只有Button组件关联了它;

  2. 执行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 后跟随的状态判断中使用的状态变量值变化时,条件渲染语句会进行更新,更新步骤如下:

  1. 评估if和else if的状态判断条件,如果分支没有变化,请无需执行以下步骤。如果分支有变化,则执行2、3步骤:

  2. 删除此前构建的所有子组件。

  3. 执行新分支的构造函数,将获取到的组件添加到if 父容器中。如果缺少适用的 else分支,则不构建任何内容。

    条件可以包括 Typescript 表达式。对于构造函数中的表达式,此类表达式不得更改应用程序状态。

使用if进行条件渲染

if 语句的每个分支都包含一个构建函数。此类构建函数必须创建一个或多个子组件。在初始渲染时,if语句会执行构建函数,并将生成的子组件添加到其父组件中。

"每当if或else if条件语句中使用的状态变量发生变化时,条件语句都会更新并重新评估新的条件值。如果条件值评估发生了变化,这意味着需要构建另一个条件分支。此时ArkUI 框架将:

  1. 删除所有以前渲染的(早期分支的)组件。

  2. 执行新分支的构造函数,将生成的子组件添加到其父组件中

代码演示

分为两种 :第一种 点击"是否真假的按钮的时候" 计数器的值会归零

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
        })
    }
  }
}
相关推荐
Wect5 小时前
LeetCode 130. 被围绕的区域:两种解法详解(BFS/DFS)
前端·算法·typescript
Dilettante2585 小时前
这一招让 Node 后端服务启动速度提升 75%!
typescript·node.js
jonjia1 天前
模块、脚本与声明文件
typescript
jonjia1 天前
配置 TypeScript
typescript
jonjia1 天前
TypeScript 工具函数开发
typescript
jonjia1 天前
注解与断言
typescript
jonjia1 天前
IDE 超能力
typescript
jonjia1 天前
对象类型
typescript
jonjia1 天前
快速搭建 TypeScript 开发环境
typescript
jonjia1 天前
TypeScript 的奇怪之处
typescript