华为HarmonyOS NEXT 原生应用开发:鸿蒙中组件的组件状态管理、组件通信 && 组件状态管理小案例(好友录)!

时间:2024-12-13 08:50:08

文章目录

  • 组件状态管理
      • 一、@State装饰器
          • 1. @State装饰器的特点
          • 2. @State装饰器的使用
      • 二、@Prop装饰器(父子单向通信)
          • 1. @Prop装饰器的特点
          • 2. @Prop装饰器的使用示例
      • 三、@Link装饰器(父子双向通信)
          • 1. @Link装饰器的特点
          • 3. @Link使用示例
      • 四、@Provide/@Consume装饰器(祖孙后代双向通信)
          • 1. 特点
          • 2. 使用条件
      • 五、@Observed装饰器和@ObjectLink装饰器
          • 1. 特点:
          • 2. 使用说明
          • 代码示例:
      • 六、拓展装饰器
      • 组件状态管理案例练习 - 好友录

组件状态管理

一、@State装饰器

1. @State装饰器的特点

● @State装饰的变量与子组件中的@Prop装饰变量之间建立单向数据同步,与@Link、@ObjectLink装饰变量之间建立双向数据同步。
● @State装饰的变量生命周期与其所属自定义组件的生命周期相同

2. @State装饰器的使用
  • 简单示例:

以下示例为@State装饰的简单类型,count被@State装饰成为状态变量,count的改变引起Button组件的刷新:
● 当状态变量count改变时,查询到只有Button组件关联了它;
● 执行Button组件的更新方法,实现按需刷新。

@Entry
@Component
struct MyComponent {
  @State count: number = 0;

  build() {
    Button(`click times: ${this.count}`)
      .onClick(() => {
        this.count += 1;
      })
  }
}
  • 该装饰器修饰的变量将别 UI 框架监视。
  • 需要注意,该变量访问权只在该组件中,且必须初始化。

二、@Prop装饰器(父子单向通信)

1. @Prop装饰器的特点

● 传递的是数据的深拷贝,每次都会拷贝数据源然后流转到子组件, 并且支持嵌套传递。
在父子组件中,使用该装饰器实现单向数据流。
● 父组件:数据源修改数据,@Prop修饰的变量都会进行覆盖变化。
● 子组件:对@Prop修饰的变量进行数据修改,并不会影响到父组件(数据源)。

在这里插入图片描述

2. @Prop装饰器的使用示例
  • 父组件发生变化,数据流向子组件,实现单向同步,而子组件修改数据,不影响父组件数据源。子组件你数据使劲修改,父组件最终修改数据,都会同步到子组件!

在这里插入图片描述

@Entry
@Component
struct CStatusPage {
  @State age: number = 0
  build() {
    Column({ space: 20 }) {
      Column({ space: 20 }) {
        Text("父组件: " + this.age)
          .fontSize(20)
        // 给子组件传参
        ChildComponent({
          age: this.age
        })
      }
      .width("50%")
      .height(200)
      .justifyContent(FlexAlign.Center)
      .padding(20)
      .backgroundColor(Color.Pink)
      Row({space: 100 }) {
        Button("父组件 + 1")
          .onClick(() => {
            this.age++
          })
      }
      .width("100%")
      .justifyContent(FlexAlign.Center)

    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)

  }
}


@Component
struct ChildComponent {
  // 接收父组件参数
  @Prop age: number = 0
  build() {
    Column() {
      Row() {
        Text("子组件:" + this.age)
          .fontSize(18)
      }
      .width("80%")
      .height(100)
      .backgroundColor(Color.Green)
      Button("子组件 + 1")
        .onClick(() => {
          this.age++
        })
    }
  }
}

三、@Link装饰器(父子双向通信)

1. @Link装饰器的特点

双向数据流:@Link装饰的变量与其父组件中的数据源共享相同的值。
● 浅拷贝,直接将引用地址进行共享,同样支持嵌套。

  • 限制条件
    ● @Link装饰器不能在@Entry装饰的组件中使用。
    ● 禁止子组件在本地初始化数据(父类数据直接流到子类,允许访问和修改,若是赋值就没有意义)。
    ● 私有,只能在所属组件内访问。
3. @Link使用示例

和上方 @Prop 大差不差,@Link 是双向数据流,父组件可以修改子,子也可以修改父组件。

@Entry
@Component
struct CStatusPage {
  @State age: number = 0
  build() {
    Column({ space: 20 }) {
      Column({ space: 20 }) {
        Text("父组件: " + this.age)
          .fontSize(20)
        // 给子组件传参
        ChildComponent({
          age: this.age
        })
      }
      .width("50%")
      .height(200)
      .justifyContent(FlexAlign.Center)
      .padding(20)
      .backgroundColor(Color.Pink)
      Row({space: 100 }) {
        Button("父组件 + 1")
          .onClick(() => {
            this.age++
          })
      }
      .width("100%")
      .justifyContent(FlexAlign.Center)

    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)

  }
}


@Component
struct ChildComponent {
  // 接收父组件参数 (需要注意不能有初始值,因为和父组件共享一份,有初始值就没有意义了)
  @Link age: number
  build() {
    Column() {
      Row() {
        Text("子组件:" + this.age)
          .fontSize(18)
      }
      .width("80%")
      .height(100)
      .backgroundColor(Color.Green)
      Button("子组件 + 1")
        .onClick(() => {
          this.age++
        })
    }
  }
}

四、@Provide/@Consume装饰器(祖孙后代双向通信)

1. 特点

双向数据流、UI框架可以跨多层检测。
使用场景: 一般在一个组件中嵌套两层一及以上的组件使用,否则直接用@Link就可以解决一层父子通信的问题。

@Provide:@Provide装饰的状态变量自动对其所有后代组件可用.

@Consume:后代通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步,与@State/@Link不同的是,他是多层级的父子组件之间传递。

2. 使用条件

使用说明:熟

  1. @Consume修饰的状态变量不能主动初始化,只能接受祖先Provide的初始化
  2. @Provide修饰的状态变量必须初始化,可以用于初始化子组件,但不能被父组件初始化。
  3. 两个组件之间状态变量名和类型需要保持一致。
  4. 组件内变量名同名了(当然,进行取别名 @Consume(“别名”)),子组件直接使用别名即可! @Provide(“别名”),子组件同样直接使用别名。
    需要注意:随着新版本的更新优化,组组件有别名的子组件就用别名,有的子组件用了父组件原名的就用其原名,不相互影响。
  • 祖先组件代码
import { SonComponents } from '../components/SonComponents'
@Entry
@Component
struct Index {
  @State message: string = 'component1';
  @Provide user: string = 'admin'
  build() {
    Column({ space: 50}) {
      Text(this.message)
        .fontSize(50)
      Row() {
        SonComponents()
      }
    }
    .height('100%')
    .width('100%')
  }
}
  • 第一层组件代码
import { Sun } from '../components/Sun'
@Component
export struct SonComponents {
  build() {
    Column() {
      Text('SonComponents2')
        .fontSize(40)

      Sun()
    }
    .width('100%')
    .height('100%')
  }
}
  • 第三层组件代码
@Component
export struct Sun {
  @Consume user: string
  build() {
    Column() {
      Text(this.user)
        .fontSize(40)
    }
    .width('100%')
    .height('100%')
  }
}

在这里插入图片描述

五、@Observed装饰器和@ObjectLink装饰器

1. 特点:

在这里插入图片描述

2. 使用说明

● @ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
● @ObjectLink中的属性可以被修改,但是不能直接覆盖自身

代码示例:
// 允许@ObjectLink装饰的数据属性赋值
this.objLink.a= ...
// 不允许@ObjectLink装饰的数据自身赋值
this.objLink= ...
class ClassA {
  public c: number;

  constructor(c: number) {
    this.c = c;
  }
}

@Observed
class ClassB {
  public a: ClassA;
  public b: number;

  constructor(a: ClassA, b: number) {
    this.a = a;
    this.b = b;
  }
}

  • ClassB被@Observed装饰,其成员变量的赋值的变化是可以被观察到的,但对于ClassA,没有被@Observed装饰,其属性的修改不能被观察到。
@ObjectLink b: ClassB

// 赋值变化可以被观察到
this.b.a = new ClassA(5)
this.b.b = 5

// ClassA类没有被@Observed装饰,嵌套在ClassB类中其属性的变化观察不到
this.b.a.c = 5

六、拓展装饰器

@Require修饰符
● 主要用于数据参数校验,添加该修饰符后,必须传递参数,故此也可以不给初始值。
@Track修饰符 (主要用于做新能优化的)

在这里插入图片描述

组件状态管理案例练习 - 好友录

  • 可以跟着源码写一遍熟悉一下,主要练习组件通信。

在这里插入图片描述

  • 数据模型文件源码
let nextId = 1

// 随机姓名数组
const NameArr: string[] = [
  "子涵", "天宇", "雨欣", "晨曦", "思琪", "佳怡", "子轩", "浩然", "梦洁",
  "文博", "子涵", "明轩", "诗涵", "子轩", "明轩", "子涵", "天宇", "雨欣",
  "晨曦", "思琪", "佳怡", "子轩", "浩然", "梦洁", "文博", "诗涵", "子轩",
  "明轩", "子涵", "天宇", "雨欣", "晨曦", "思琪", "佳怡", "子轩", "浩然",
  "梦洁", "文博", "诗涵", "子轩", "明轩", "子涵", "天宇", "雨欣", "晨曦",
  "思琪", "佳怡", "子轩", "浩然", "梦洁", "文博", "诗涵", "子轩", "明轩",
  "子涵", "天宇", "雨欣", "晨曦", "思琪", "佳怡", "子轩", "浩然", "梦洁",
  "文博", "诗涵", "子轩", "明轩", "子涵", "天宇", "雨欣", "晨曦", "思琪",
  "佳怡", "子轩", "浩然", "梦洁", "文博", "诗涵", "子轩", "明轩", "小欣",
  "梦洁", "文博", "诗涵", "子轩", "明轩", "子涵", "天宇", "雨欣", "晨曦",
  "思琪", "佳怡", "子轩", "浩然", "梦洁", "文博", "诗涵", "子轩", "明轩",
  "欣姚"
]
// 随机生成的 手机号码


@Observed
export class Person {
  id: number
  name: string
  phone: string
  isStar: boolean = false

  constructor(name: string, phone: string) {
    this.id = nextId++
    this.name = name
    this.phone = phone
  }
}

export function getPhonePerson (): Person {
  let personArr = new Person(randomNameHandle(), randomPhoneNumberHandle())
  return personArr
}

// 随机生成姓名
export function randomNameHandle(): string {
  let name: string = ""
  let randomNameNumber: number = Math.floor(Math.random() * 100 )
  name = NameArr[randomNameNumber]
  return name
}



// 生成随机的手机号
export function randomPhoneNumberHandle(): string {
  // 生成 11 位随机数字
  let phoneNumber = '';
  for (let i = 0; i < 11; i++) {
    phoneNumber += Math.floor(Math.random() * 10).toString();
  }
  return phoneNumber;
}
  • index文件源码
import { Person, getPhonePerson } from "./model/DataModel"
import { promptAction } from '@kit.ArkUI'

@Entry
@Component
struct Index {
  // 当前的id值
  @State PhonePerson: Person[] = [getPhonePerson(), getPhonePerson(), getPhonePerson()]
  // id容器,我需要得到子组件给我的当前id值
  @State currentContactID: number = -1

  // false 选择, true 取消
  @State titleTextBoolean: boolean = false

  @State deleteArrList: number[] = []
  build () {
    Column() {
      // 标题
      Row({ space: 10 }) {
        Text('联系人')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
        Blank()
        Button(this.titleTextBoolean ? "取消" : "选择")
          .fontColor(Color.White)
          .fontSize(14)
          .backgroundColor(this.titleTextBoolean ? Color.Red : "#007dfe")
          .onClick(() => {
            this.titleTextBoolean = ! this.titleTextBoolean
            this.deleteArrList = []
          })
        Button(" + ")
          .fontColor(Color.White)
          .fontSize(14)
          .onClick(() => {
            // 新增联系人(往数组追加对象)
            this.PhonePerson.push(getPhonePerson())
          })
      }
      .width('100%')

      // 主体列表
      List({ space: 10 }) {
        ForEach(this.PhonePerson, (item: Person, index: number) => {
          ListItem() {
            // 联系人项目
            ContactPersonComponent({
              // 将当前对象传递下去
              item: item,
              currentContactID: this.currentContactID,
              titleTextBoolean: this.titleTextBoolean,
              deleteArrList: this.deleteArrList
            })

          }
        })
      }
      .margin({ top: 10 })
      .layoutWeight(1)

      // 底部按钮
      if (this.titleTextBoolean) {
        Button