华为HarmonyOS NEXT 原生应用开发:鸿蒙中组件的组件状态管理、组件通信 && 组件状态管理小案例(好友录)!
文章目录
组件状态管理
一、@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. 使用条件
使用说明:熟
- @Consume修饰的状态变量不能主动初始化,只能接受祖先Provide的初始化
- @Provide修饰的状态变量必须初始化,可以用于初始化子组件,但不能被父组件初始化。
- 两个组件之间状态变量名和类型需要保持一致。
- 组件内变量名同名了(当然,进行取别名 @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(this.deleteArrList.length === 0 ? "取消" : "删除")
.fontColor(Color.White)
.backgroundColor(this.deleteArrList.length === 0 ? Color.Gray : Color.Red)
.onClick(() => {
if (this.deleteArrList.length === 0) {
promptAction.showToast({
message: "操作取消!",
textColor: "#FFCCAA"
})
this.titleTextBoolean = false
} else {
for(let item = 0; item < this.PhonePerson.length; item++) {
for(let i = 0; i < this.deleteArrList.length; i++) {
if (this.PhonePerson[item].id === this.deleteArrList[i]) {
this.PhonePerson.splice(item, 1)
}
}
}
// 复原操作
this.titleTextBoolean = false
// 每次删除完成后,重置数组
this.deleteArrList = []
promptAction.showToast({
message: "删除成功!",
textColor: "#8ADAB2"
})
}
})
}
}
.width('100%')
.height('100%')
.padding(15)
.backgroundColor("#ffd6d4d4")
}
}
@Component
struct ContactPersonComponent {
@ObjectLink item: Person
@State showFlag: boolean = false
@Link @Watch("onClickContactChange") currentContactID: number
@Prop titleTextBoolean: boolean
@Link deleteArrList: number[]
onClickContactChange() {
// 只要id发生变化,则将id 不等于当前的联系人详细信息项关闭
if(this.currentContactID != this.item.id) {
this.showFlag = false
}
}
build() {
Column({ space: 15 }) {
Row({ space: 10 }) {
if (this.titleTextBoolean) {
Toggle({ type: ToggleType.Checkbox}) // {type: 按钮类型, isOn: 按钮是否选中状态}
.selectedColor("#FFB0B0") // Toggle按钮组件 选中时候的颜色
.onChange((value: boolean) => {
// value值为true和false,二者之间随时切换!如果用户点击就为true,在次点击就为false
if (value) {
this.deleteArrList.push(this.item.id)
} else {
// 获取该元素在deleteArrList的位置,以便后续删除操作
let index: number = this.deleteArrList.indexOf(this.item.id)
this.deleteArrList.splice(index, 1)
}
})
}
Image($r("app.media.startIcon"))
.width(35)
.height(35)
Text(this.item.name)
Blank()
Image(this.item.isStar ? $r("app.media.select_collection") : $r("app.media.collection"))
.width(24)
.onClick(() => {
this.item.isStar = ! this.item.isStar
// 弹框提示
if (this.item.isStar) {
promptAction.showToast({
message: `收藏成功!`,
alignment: Alignment.Bottom,
offset: { dx: 0, dy: -200},
textColor: "#9ADE7B"
})
} else {
promptAction.showToast({
message: `取消收藏!`,
alignment: Alignment.Bottom,
offset: { dx: 0, dy: -200},
textColor: Color.Red
})
}
})
}
.width('100%')
.height(60)
if (this.showFlag) {
Divider()
.strokeWidth(2)
.color(Color.Black)
Row({ space: 20 }) {
Text("手机号码:")
Text(this.item.phone)
}
.width('100%')
.justifyContent(FlexAlign.Start)
.padding(15)
}
}
.backgroundColor(Color.White)
.padding({ top: 5, bottom: 5, left: 10, right: 10 })
.borderRadius(15)
.onClick(() => {
this.showFlag = ! this.showFlag
this.currentContactID = this.item.id // 将当前点击的联系人 id 给父组件
})
}
}
原文地址:https://blog.csdn.net/Forever_Hopeful/article/details/144435578
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!