AI 摘要

文章聚焦 HarmonyOS 4.0 ArkTS 进阶:@State 对普通对象可监听,嵌套对象与数组需整体替换触发刷新;条件渲染 if/else 会销毁组件,ForEach 完成列表循环;@Builder 支持全局/局部复用 UI,参数按值传递不响应状态,按引用($$)传递可随状态刷新。

组件状态进阶

对象类型状态

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-state-0000001474017162-V2#section1822514413343

当 @State 装饰的是一个普通的对象类型时,@State 能监听到数据的变化。


示例:对象类型的状态监听

Index 组件(src/main/ets/pages/Index.ets):

// 定义对象模型
class User {
  name: string
  age: number
}

@Entry
@Component
struct Index {
  // 实例化User对象
  @State
  user: User = {
    name: '张三',
    age: 18
  }

  build() {
    Row() {
      Column({space: 10}) {
        Text('姓名:' + this.user.name)
        Text('年龄:' + this.user.age)

        Button('age++')
          .onClick(() => {
            this.user.age++
          })
      }
      .width('100%')
    }
    .padding(20)
  }
}

示例效果:


嵌套对象类型状态

当 @State 装饰的是一个嵌套的对象类型时,@State 无法监听到嵌套的数据的变化。


示例:嵌套对象类型的状态监听

Index 组件(src/main/ets/pages/Index.ets):

// 定义对象模型
class Role {
  roleName: string
}

class User {
  name: string
  age: number
  // User嵌套Role
  role: Role
}

@Entry
@Component
struct Index {
  // 实例化User对象
  @State
  user: User = {
    name: '张三',
    age: 18,
    role: {
      roleName: '技术部'
    }
  }

  build() {
    Row() {
      Column({space: 10}) {
        Text('姓名:' + this.user.name)
        Text('年龄:' + this.user.age)
        Text('部门名称:' + this.user.role.roleName)

        Button('age++')
          .onClick(() => {
            this.user.age++
          })

        Button('role change')
          .onClick(() => {
            // 在JavaScript中引用数据类型,变量保存的值是引用地址
            // 直接修改this.user.role.roleName导致user.role的内存地址未变化
            // @State无法监测到数据的变化
            // this.user.role.roleName = '综合部'

            // 将旧的role对象复制到一个新对象
            // ...符号表示拷贝
            const role = {...this.user.role}
            // 修改roleName
            role.roleName = '综合部'

            // 将新的role对象赋值给user对象
            this.user.role = role
          })
      }
      .width('100%')
    }
    .padding(20)
  }
}

示例效果:


对象数组类型状态

当 @State 装饰的是一个对象数组类型时,@State 无法监听到数组内部的数据的变化。


示例:对象数组类型的状态监听

Index 组件(src/main/ets/pages/Index.ets):

class User {
  name: string
  age: number
}

@Entry
@Component
struct Index {
  // 实例化User对象数组
  @State
  userList: User[] = [
    { name: '张三', age: 18 },
    { name: '李四', age: 19 },
    { name: '王五', age: 20 }
  ]


  build() {
    Row() {
      Column({space: 10}) {
        Text('姓名:' + this.userList[0].name)
        Text('年龄:' + this.userList[0].age)

        Button('age++')
          .onClick(() => {
            const user = {...this.userList[0]}
            user.age++
            this.userList[0] = user
          })
      }
      .width('100%')
    }
    .padding(20)
  }
}

示例效果:


界面渲染

条件渲染

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-rendering-control-ifelse-0000001524177637-V2

条件渲染可根据应用的不同状态,使用 if、else 和 else if 渲染对应状态下的 UI 内容。

  • 条件渲染是根据状态数据进行判断展示不同 UI。
  • 条件渲染时会销毁和创建组件(包括自定义组件和内置组件),组件状态将不会保留。

示例:条件渲染

Index 组件(src/main/ets/pages/Index.ets):

@Entry
@Component
struct Index {
  // 是否显示加载动效组件
  @State
  loading: boolean = true

  build() {
    Row() {
      Column({space: 10}) {
        // 按钮点击切换加载动效
        Button('change')
          .onClick(() => {
            this.loading = !this.loading
          })

        // 条件判断
        if(this.loading) {
          LoadingProgress()
        } else {
          Text('Hello HarmonyOS')

          // 条件渲染会销毁和创建组件,因此切换时输入框的数据不会被保留
          TextInput()
        }
      }
      .width('100%')
    }
    .height('100%')
  }
}

示例效果:


循环渲染

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-rendering-control-foreach-0000001524537153-V2

ForEach 基于数组类型数据来进行循环渲染,需要与容器组件配合使用。

ForEach 的基本用法:

ForEach(
  arr: Array,
  itemGenerator: (item: any, index?: number) => void,
  keyGenerator?: (item: any, index?: number) => string
)

ForEach 的基本参数:

名称类型描述
arrarray要遍历的列表
itemGenerator(item: any, index?: number) => void组件生成函数
item:遍历后每个数据项的别名
index:数据下标索引
keyGenerator(item: any, index?: number) => string键值生成函数,为数据源 arr 的每个数组项生成唯一且持久的键值

示例:循环渲染

Index 组件(src/main/ets/pages/Index.ets):

class User {
  name: string
  age: number
}

@Entry
@Component
struct Index {
  // 实例化User对象数组
  @State
  userList: User[] = [
    { name: '张三', age: 18 },
    { name: '李四', age: 19 },
    { name: '王五', age: 20 },
    { name: '唐三藏', age: 21 },
    { name: '孙悟空', age: 22 },
    { name: '猪八戒', age: 23 },
    { name: '沙悟净', age: 24 }
  ]

  build() {
    Row() {
      Column({space: 10}) {
        ForEach(this.userList, (item: User, index: number) => {
          Text('id is ' + index + ', name is ' + item.name + ', age is ' + item.age)
        })
      }
      .width('100%')
    }
    .padding(20)
  }
}

示例效果:


自定义构建函数

@Builder

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-builder-0000001524176981-V2

ArkUI 还提供了一种更轻量的 UI 元素复用机制 @Builder,@Builder 所装饰的函数遵循 build() 函数语法规则,开发者可以将重复使用的 UI 元素抽象成一个方法,在 build 方法里调用。

自定义构建函数支持全局定义和组件内定义。


示例:自定义构建函数

Index 组件(src/main/ets/pages/Index.ets):

// 全局定义自定义构建函数
@Builder
function publicBuilderFunction() {
  Text('查看更多 >>')
    .fontColor(Color.Gray)
}

@Entry
@Component
struct Index {
  // 组件内定义自定义构建函数
  @Builder
  myBuilderFunction() {
    Text('查看更多 >>')
      .fontColor(Color.Gray)
  }

  build() {
    Column(){
      Row() {
        Text('评价 (2000)')
          .layoutWeight(1)
          .fontWeight(FontWeight.Bold)

        // 调用组件内自定义构建函数
        this.myBuilderFunction()
      }
      .padding(10)

      Row() {
        Text('推荐')
          .layoutWeight(1)

        // 调用全局自定义构建函数
        publicBuilderFunction()
      }
      .padding(10)
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#ffeaeaea')
  }
}

示例效果:


传递参数

自定义构建函数的参数传递有按值传递和按引用传递两种。

按值传递:调用 @Builder 装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起 @Builder 方法内的 UI 刷新。


示例:按值传递参数

Index 组件(src/main/ets/pages/Index.ets):

@Entry
@Component
struct Index {
  // 组件内定义自定义构建函数
  @Builder
  myBuilderFunction(title: string) {
    Text(title)
      .fontColor(Color.Gray)
  }

  build() {
    Column(){
      Row() {
        Text('评价 (2000)')
          .layoutWeight(1)
          .fontWeight(FontWeight.Bold)

        // 调用组件内自定义构建函数
        // 传递title参数
        this.myBuilderFunction('查看更多 >>')
      }
      .padding(10)
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#ffeaeaea')
  }
}

示例效果:


按引用传递:传递的参数可为状态变量,且状态变量的改变会引起 @Builder 方法内的 UI 刷新。


示例:按引用传递参数

Index 组件(src/main/ets/pages/Index.ets):

@Entry
@Component
struct Index {
  @State
  rate: number = 0

  // 组件内定义自定义构建函数
  // $$:按引用传递参数的范式
  @Builder
  myBuilderFunction($$: {title: string}) {
    Text($$.title)
      .fontColor(Color.Gray)
  }

  build() {
    Column(){
      Row() {
        Text('评价 (2000)')
          .layoutWeight(1)
          .fontWeight(FontWeight.Bold)

        // 调用组件内自定义构建函数
        // 引用传递title参数
        // this.rate变化时,UI变化
        this.myBuilderFunction({title: 'rate is ' + this.rate})
      }
      .padding(10)

      Row() {
        Button('rate++')
          .onClick(() => {
            this.rate++
          })
      }
      .padding(10)
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#ffeaeaea')
  }
}

示例效果: