赞
踩
Harmony OS Next 开发专栏:
点击前往: HarmonyOS开发专栏
// 实体类 class Item { name: string image: ResourceStr price: number discount: number // 构造方法 constructor(name: string, image: ResourceStr, price: number, discount: number) { this.name = name this.image = image this.price = price this.discount = discount } } @Entry @Component struct ItemPage { // 数据 private items: Array<Item> = [ new Item('华为Mate60Pro', $r('app.media.mate60'), 6999, 1000), new Item('华为Mate60Pro', $r('app.media.mate60'), 6999, 0), new Item('华为Mate60Pro', $r('app.media.mate60'), 6999, 0), new Item('华为Mate60Pro', $r('app.media.mate60'), 6999, 0), new Item('华为Mate60Pro', $r('app.media.mate60'), 6999, 0) ] build() { Column({space: 8}) { Row() { Text('商品列表') .fontSize(30) .fontWeight(FontWeight.Bold) } .width('100%') .margin({bottom: 20}) // ForEach ForEach( this.items, (item: Item) => { Row({ space: 10}){ Image(item.image) .width(100) Column({ space: 4}) { if (item.discount) { Text(item.name) .fontSize(20) .fontWeight(FontWeight.Bold) Text('原价:¥' + item.price) .fontSize(18) .fontColor('#FFCCAA') .decoration({ type: TextDecorationType.LineThrough }) Text('折扣价:¥' + (item.price - item.discount)) .fontSize(18) .fontColor('#FFCCAA') Text('补贴:¥' + item.discount) .fontSize(18) .fontColor('#FFCCAA') } else { Text(item.name) .fontSize(20) .fontWeight(FontWeight.Bold) Text('¥' + item.price) .fontSize(18) .fontColor('#FFCCAA') } } .height('100%') .alignItems(HorizontalAlign.Start) } .width('90%') .height(120) .backgroundColor('#FFF') .borderRadius(20) .margin({left: 24, right: 24}) .padding(10) }, ) } .backgroundColor('#EEE') .height('100%') } }
// 使用 List 列表容器包裹 List({ space: 10}) { // ForEach ForEach( this.items, (item: Item) => { ListItem() { Row({ space: 10}){ Image(item.image) .width(100) Column({ space: 4}) { if (item.discount) { Text(item.name) .fontSize(20) .fontWeight(FontWeight.Bold) Text('原价:¥' + item.price) .fontSize(18) .fontColor('#FFCCAA') .decoration({ type: TextDecorationType.LineThrough }) Text('折扣价:¥' + (item.price - item.discount)) .fontSize(18) .fontColor('#FFCCAA') Text('补贴:¥' + item.discount) .fontSize(18) .fontColor('#FFCCAA') } else { Text(item.name) .fontSize(20) .fontWeight(FontWeight.Bold) Text('¥' + item.price) .fontSize(18) .fontColor('#FFCCAA') } } .height('100%') .alignItems(HorizontalAlign.Start)
// 创建一个自定义组件 @Component struct Header { private title: ResourceStr build() { Row() { Image($r('app.media.return')) .width(30) Text(this.title) .fontSize(30) .fontWeight(FontWeight.Bold) .height(40) Blank() Image($r('app.media.refresh')) .width(30) } .width('100%') .height(30) } }
使用自定义组件
// 调用自定义组件
Header({ title: '商品列表'})
.margin(30) // 修改自定义组件样式
// 创建一个全局自定义组件 @Component // 1、全局自定义组件需要我们进行导出 export export struct Header { private title: ResourceStr = '' build() { Row() { Image($r('app.media.return')) .width(30) Text(this.title) .fontSize(30) .fontWeight(FontWeight.Bold) .height(40) Blank() Image($r('app.media.refresh')) .width(30) } .width('100%') .height(30) } } // 2、导入全局自定义组件 import {Header} from '../components/CommonComponents'
从上面代码可以看出了,我们页面结构是不是很臃肿,此时我们使用自定义构建函数解决这个问题是非常好解决的,就和我们其他编程语言里,将代码封装起来,提高可读性
// 局部自定义构建函数(在页面组件内声明) @Builder ItemCard (item: Item) { Row({ space: 10}){ Image(item.image) .width(100) Column({ space: 4}) { if (item.discount) { Text(item.name) .fontSize(20) .fontWeight(FontWeight.Bold) Text('原价:¥' + item.price) .fontSize(18) .fontColor('#FFCCAA') .decoration({ type: TextDecorationType.LineThrough }) Text('折扣价:¥' + (item.price - item.discount)) .fontSize(18) .fontColor('#FFCCAA') Text('补贴:¥' + item.discount) .fontSize(18) .fontColor('#FFCCAA') } else { Text(item.name) .fontSize(20) .fontWeight(FontWeight.Bold) Text('¥' + item.price) .fontSize(18) .fontColor('#FFCCAA') } } .height('100%') .alignItems(HorizontalAlign.Start) } .width('90%') .height(120) .backgroundColor('#FFF') .borderRadius(20) .margin({left: 15, right: 10}) .padding(10) }
// 全局自定义构建函数(在页面组件外声明) @Builder function ItemCard (item: Item) { Row({ space: 10}){ Image(item.image) .width(100) Column({ space: 4}) { if (item.discount) { Text(item.name) .fontSize(20) .fontWeight(FontWeight.Bold) Text('原价:¥' + item.price) .fontSize(18) .fontColor('#FFCCAA') .decoration({ type: TextDecorationType.LineThrough }) Text('折扣价:¥' + (item.price - item.discount)) .fontSize(18) .fontColor('#FFCCAA') Text('补贴:¥' + item.discount) .fontSize(18) .fontColor('#FFCCAA') } else { Text(item.name) .fontSize(20) .fontWeight(FontWeight.Bold) Text('¥' + item.price) .fontSize(18) .fontColor('#FFCCAA') } } .height('100%') .alignItems(HorizontalAlign.Start) } .width('90%') .height(120) .backgroundColor('#FFF') .borderRadius(20) .margin({left: 15, right: 10}) .padding(10) }
// 使用 List 列表容器包裹
List({ space: 10}) {
// ForEach
ForEach(
this.items,
(item: Item) => {
ListItem() {
// this.ItemCard(item) 局部调用方式
// ItemCard(item) 全局调用方式
}
},
)
}
// 局部公共样式函数
@Styles pageStyle () {
.width('100%')
.height('100%')
.backgroundColor('#EEE')
.padding(20)
}
// 全局公共样式含数据
@Styles function pageStyle () {
.width('100%')
.height('100%')
.backgroundColor('#EEE')
.padding(20)
}
.pageStyle() // 调用全局样式函数
这里我们只能使用@Exdent
进行修饰// 继承属性:全局封装特有属性样式函数
@Extend(Text) function priceText () {
.fontSize(18)
.fontColor('#FFCCAA')
}
如下代码示例:
```@Entry @Component struct StateDemoCode { @State message: string = 'Hello World' build() { Column() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) .onClick(() => { this.message = 'Hello ArkTS!' }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } }
普通的对象也是可以及时更新的:
/** * 状态管理 */ class Person { name: string age: number constructor (name: string, age: number) { this.name = name this.age = age } } @Entry @Component struct StateDemoCode { @State p: Person = new Person('LiuJinTao', 18) build() { Column() { Text(`姓名${this.p.name} : ${this.p.age}`) .fontSize(40) .fontWeight(FontWeight.Bold) .onClick(() => { this.p.age++ }) } .width('100%') .height('100%') } }
下面是无法及时更新驶入的展示:
@State p: Person = new Person('LiuJinTao', 18, new Person('yxy', 18)) // 这里就是 嵌套对象 Text(`姓名${this.p.name} : ${this.p.age}`) .fontSize(40) .fontWeight(FontWeight.Bold) .onClick(() => { this.p.age++ // 外层对象元素 }) Text(`姓名${this.p.gf.name} : ${this.p.gf.age}`) .fontSize(40) .fontWeight(FontWeight.Bold) .onClick(() => { this.p.gf.age++ // 嵌套对象无法及时驱动视图 })
不是嵌套我们数据更新了,视图就会立马更新,而下面的,我们点击他不会立马更新视图,因为它是嵌套在对象内的对象,所有无法更新视图。
数组里面嵌套对象,也是不能及时更新
ForEach(
this.gfs,
(p: Person, index) => {
Text(`${p.name} : ${p.age}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
p.age++ // 修改数组里面的值,视图不会更新哦!
})
}
// 任务类 class Task { static id: number = 1 // 任务名称 name: string = `任务${Task.id++}` // 任务状态是否完成 finished: boolean = false } // 统一的卡片样式函数 @Styles function card () { .width('95%') .padding(20) .backgroundColor(Color.White) .borderRadius(15) .shadow({radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4}) } // 任务完成样式函数(Text组件特有属性) @Extend(Text) function finishedTask() { .decoration({ type: TextDecorationType.LineThrough}) .fontColor('#B1B2B1') } @Entry @Component struct PropPage { // 任务总数量 @State totalTask: number = 0 // 已完成任务数量 @State finishTask: number = 0 // 任务列表 @State tasks: Task[] = [] // 局部自定义函数 handleTaskChange() { // 1. 更新总任务 this.totalTask = this.tasks.length // 总任务就是数组的长度 // 2. 更新已完成的任务 this.finishTask = this.tasks.filter(item => item.finished).length // 通过filter方法过滤 finished状态为true的个数,然后赋值更新! } build() { Column({ space: 10}) { // 1. 任务进度 Row() { Text('任务进度:') .fontSize(30) .fontWeight(FontWeight.Bold) // 层叠容器 Stack(){ // 进度条组件 Progress({ value: this.finishTask, total: this.tasks.length, // 最大任务数等于数组长度 type: ProgressType.Ring }) .width(100) // 任务进度对比数 Row(){ Text(this.finishTask.toString()) .fontSize(20) Text(" / " + this.totalTask.toString()) .fontSize(20) } } } .width('100%') .justifyContent(FlexAlign.SpaceEvenly) .margin({top: 20, bottom: 10}) // 2. 新增任务按钮 Row(){ Button('新增任务') .width(100) .backgroundColor('#36D') .onClick(() => { // 1. 新增任务条数 this.tasks.push(new Task()) // 将任务类添加到数组中 // 2. 更新总任务 // this.totalTask = this.tasks.length // 总任务就是数组的长度 this.handleTaskChange() }) } .margin({bottom: 24}) // 3. 任务状态列表 List({ space: 10, initialIndex: 0}) { // 遍历列表 ForEach( this.tasks, (item: Task, index) => { ListItem() { Row(){ Text(item.name) .fontSize(20) Checkbox() // 是否选中就是取决于开关 .select(item.finished) .onChange(change => { // 1. 更新勾选状态值 item.finished = change // 3. 更新已完成的任务 // this.finishTask = this.tasks.filter(item => item.finished).length // 通过filter方法过滤 finished状态为true的个数,然后赋值更新! this.handleTaskChange() }) } .card() .justifyContent(FlexAlign.SpaceBetween) } // 左滑事件调用按钮模块函数 .swipeAction({ end: this.DeleteButton(index) }) }) } .width('100%') .alignListItem(ListItemAlign.Center) .layoutWeight(1) } .width('100%') .height('100%') .backgroundColor('#F1F2F3') .padding(10) } /** * DeleteButton 左滑删除按钮局部构建函数 * @param index */ @Builder DeleteButton (index: number) { Button() { Image($r('app.media.button_delete')) .width(20) // SVG图片颜色的设置 .fillColor(Color.White) } .width(40) .height(40) .type(ButtonType.Circle) // Button组件按压效果 .stateEffect(true) .backgroundColor(Color.Red) .margin(5) .onClick(() => { // 1、点击删除后,更新任务列表 this.tasks.splice(index, 1) // 2. 调用方法,更新任务进度的总数个完成数量 this.handleTaskChange() }) } }
单项数据和双向数据的原理就和深浅拷贝一样
第一步:
创建完毕就开始对代码改造了:
基于@Prop 和 @ Link组件通信技术进行优化代码:”
首先是任务进度模块
@Component export struct TaskProgress { // 注意: 这里是父组件传递过来的数据,父组件数据被@State修饰,子组件就不能再使用@State修饰了 // 注意2:@Prop在之前版本是不允许初始化的,可是现在最新版是允许的。新版本两者都可以使用兼容 @Prop finishTask: number @Prop totalTask: number build() { // 1. 任务进度 Row() { Text('任务进度:') .fontSize(30) .fontWeight(FontWeight.Bold) // 层叠容器 Stack(){ // 进度条组件 Progress({ value: this.finishTask, total: this.totalTask, type: ProgressType.Ring }) .width(100) // 任务进度对比数 Row(){ Text(this.finishTask.toString()) .fontSize(20) Text(" / " + this.totalTask.toString()) .fontSize(20) } } } .width('100%') .justifyContent(FlexAlign.SpaceEvenly) .margin({top: 20, bottom: 10}) } }
其次就是任务列表模块
/** * 任务列表模块 */ // 任务类 class Task { static id: number = 1 // 任务名称 name: string = `任务${Task.id++}` // 任务状态是否完成 finished: boolean = false } @Component export struct TaskList { // 任务总数量 @Link totalTask: number //@Link接受的参数必须是引用($),并且不能给初始值 // 已完成任务数量 @Link finishTask: number // 任务列表 @State tasks: Task[] = [] // 局部自定义函数 handleTaskChange() { // 1. 更新总任务 this.totalTask = this.tasks.length // 总任务就是数组的长度 // 2. 更新已完成的任务 this.finishTask = this.tasks.filter(item => item.finished).length // 通过filter方法过滤 finished状态为true的个数,然后赋值更新! } build() { Column() { // 2. 新增任务按钮 Row() { Button('新增任务') .width(200) .backgroundColor('#36D') .onClick(() => { // 1. 新增任务条数 this.tasks.push(new Task()) // 将任务类添加到数组中 // 2. 更新总任务 // this.totalTask = this.tasks.length // 总任务就是数组的长度 this.handleTaskChange() }) } .margin({ bottom: 24 }) // 3. 任务状态列表 List({ space: 10, initialIndex: 0}) { // 遍历列表 ForEach( this.tasks, (item: Task, index) => { ListItem() { Row(){ Text(item.name) .fontSize(20) Checkbox() // 是否选中就是取决于开关 .select(item.finished) .onChange(change => { // 1. 更新勾选状态值 item.finished = change // 3. 更新已完成的任务 // this.finishTask = this.tasks.filter(item => item.finished).length // 通过filter方法过滤 finished状态为true的个数,然后赋值更新! this.handleTaskChange() }) } .card() .justifyContent(FlexAlign.SpaceBetween) } // 左滑事件调用按钮模块函数 .swipeAction({ end: this.DeleteButton(index) }) }) } .width('100%') .alignListItem(ListItemAlign.Center) .layoutWeight(1) } } /** * DeleteButton 左滑删除按钮局部构建函数 * @param index */ @Builder DeleteButton (index: number) { Button() { Image($r('app.media.button_delete')) .width(20) // SVG图片颜色的设置 .fillColor(Color.White) } .width(40) .height(40) .type(ButtonType.Circle) // Button组件按压效果 .stateEffect(true) .backgroundColor(Color.Red) .margin(5) .onClick(() => { // 1、点击删除后,更新任务列表 this.tasks.splice(index, 1) // 2. 调用方法,更新任务进度的总数个完成数量 this.handleTaskChange() }) } } // 统一的卡片样式函数 @Styles function card () { .width('95%') .padding(20) .backgroundColor(Color.White) .borderRadius(15) .shadow({radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4}) } // 任务完成样式函数(Text组件特有属性) @Extend(Text) function finishedTask() { .decoration({ type: TextDecorationType.LineThrough}) .fontColor('#B1B2B1') }
最后,我们再页面调用我们自定义的功能模块
import { TaskList } from '../components/TaskList' import { TaskProgress } from '../components/TaskProgress' @Entry @Component struct PropPage { // 任务总数量 @State totalTask: number = 0 // 已完成任务数量 @State finishTask: number = 0 build() { Column({ space: 10 }) { // 1、任务进度 TaskProgress({ totalTask: this.totalTask, finishTask: this.finishTask }) // 2、任务列表 TaskList({ totalTask: $totalTask, finishTask: $finishTask }) } .width('100%') .height('100%') .backgroundColor('#F1F2F3') .padding(10) } }
@Provide/@Consume装饰的状态变量有以下特性:
1、Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,2、Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
后代通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步,与@State/@Link不同的是,前者可以在多层级的父子组件之间传递。
3、Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,变量类型必须相同。
使用场景
在下面的示例是与后代组件双向同步状态@Provide和@Consume场景。当分别点击CompA和CompD组件内Button时,reviewVotes 的更改会双向同步在CompA和CompD中。
@Component struct CompD { // @Consume装饰的变量通过相同的属性名绑定其祖先组件CompA内的@Provide装饰的变量 @Consume reviewVotes: number; build() { Column() { Text(`reviewVotes(${this.reviewVotes})`) Button(`reviewVotes(${this.reviewVotes}), give +1`) .onClick(() => this.reviewVotes += 1) } .width('50%') } } @Component struct CompC { build() { Row({ space: 5 }) { CompD() CompD() } } } @Component struct CompB { build() { CompC() } } @Entry @Component struct CompA { // @Provide装饰的变量reviewVotes由入口组件CompA提供其后代组件 @Provide reviewVotes: number = 0; build() { Column() { Button(`reviewVotes(${this.reviewVotes}), give +1`) .onClick(() => this.reviewVotes += 1) CompB() } } }
概述
@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:
被@Observed装饰的类,可以被观察到属性的变化;
子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
单独使用@Observed是没有任何作用的,需要搭配@ObjectLink或者@Prop使用。
限制条件
使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。
@ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
代码展示如下:
// 任务类
@Observed
class Task {
static id: number = 1
// 任务名称
name: string = `任务${Task.id++}`
// 任务状态是否完成
finished: boolean = false
}
// 渲染组件 @Component struct TaskItem { @ObjectLink item: Task onTaskChange: () => void = () => {}; // 定义一个函数变量参数,接收父组件传过来的函数 build() { Row() { // 是否添加 if (this.item.finished) { Text(this.item.name) .fontSize(20) .finishedTask() } else { Text(this.item.name) .fontSize(20) } Checkbox()// 是否选中就是取决于开关 .select(this.item.finished) .onChange(change => { // 1. 更新勾选状态值 this.item.finished = change // 3. 更新已完成的任务 // this.finishTask = this.tasks.filter(item => item.finished).length // 通过filter方法过滤 finished状态为true的个数,然后赋值更新! // 添加一个判断,如果传过来参数为空,那么表示函数传递失败,不执行! if (this.onTaskChange) { this.onTaskChange() } }) } .card() .justifyContent(FlexAlign.SpaceBetween) }
// 调用处理后的渲染类
TaskItem({ item: item, onTaskChange: () => this.handleTaskChange() })
这里我们想要嵌套的对象或者数组里面的元素为对象的时候,当嵌套内的对象或者数组元素数据修改了,通过观察我们是无法更新视图的,测试就得使用 @Observed装饰器和@ObjectLink装饰器
- 大白话来说就是,@Observed将类或者数据的引用传递给了呗ObjectLink修饰的属性,此时修改引用,那么无论是外层的还是里层的数据都被修改了,视图自然也就会及时更新!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。