一、状态管理
1、概述
声明式UI中,是以状态驱动视图更新:
- 状态(State):指驱动UI更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染。
- 视图(View):UI渲染,指将build方法内的UI描述和@Builder装饰的方法内的UI描述映射到界面。
2、@State
@State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就可以触发其直接绑定UI组件的刷新。当状态改变时,UI会发生对应的渲染改变。
- @State装饰器标记的变量必须初始化,不能为空值,可以作为其子组件单向和
双向同步的数据源;- @State支持基本数据类型,数组,类 ;
- 嵌套类型以及数组中的对象属性无法触发视图更新。
3、@Prop和@Link
当父子组件之间需要数据同步时,可使用@Prop和@Link装饰器。
@Prop | @Link | |
同步类型 | 单项同步 | 双向同步 |
允许装饰的变量类型 |
|
|
初始化方式 | 不允许子组件初始化,变量是可变的,但修改不会同步回父组件 | 父组件传递,禁止子组件初始化,变量的修改是同步的,父组件的更新也会同步给子组件 |
4、@Provide和@Consume
@Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。
@Provide | 基本数据类型,数组,类 | @Provide作为数据提供方,可以更新其子孙节点数据,并触发页面渲染 |
@Consume | 基本数据类型,数组,类 | 感应到@Provide装饰的变量更新后,会触发当前自定义组件的重新渲染 |
注:
- 在使用@Provide和@Consume时应避免循环引用,否则会造成死循环;
- @Consume不可设置默认初始值;
5、@ObjectLink和@Observed
@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步。
@ObjectLink | 被@Observed所装饰类的对象 | 父组件或者其他兄弟组件内关联的状态数据都会更新UI |
@Observed | 类 | 应用于类UI页面管理该类中的数据变更 |
注:@ObjectLink装饰的变量是私有变量,只能在组件内访问。
练习:
一些小tips:
(1)基础组件--Progress:进度条,用于显示内容加载或操作处理的进度。
Progress(options: {value: number, total?: number, type?: ProgressType})
其中,value用于设置初始进度值,total用于设置进度总长度,type用于设置Progress样式。 Progress有5种可选类型,通过ProgressType可以设置进度条样式,ProgressType类型包括:
- ProgressType.Linear(线性样式)
- ProgressType.Ring(环形无刻度样式)
- ProgressType.ScaleRing(环形有刻度样式)
- ProgressType.Eclipse(圆形样式)
- ProgressType.Capsule(胶囊样式)
(2)基础组件--Checkbox:提供多选框组件,通常用于某选项的打开或关闭。
Checkbox(options?: {name?: string, group?: string })
其中,name用于设置多选框名称,group用于设置多选框的群组名称。其属性select用于设置多选框是否选中,selectedColor用于设置多选框选中状态颜色。可添onChange()事件。
(3)容器组件--叠层布局(Stack):用于在屏幕上预留一块区域来显示组件中的元素,提供元素可以重叠的布局。层叠布局通过Stack容器组件实现位置的固定定位与层叠,容器中的子元素依次入栈,后一个子元素覆盖前一个子元素,子元素可以叠加,也可以设置位置。
Stack组件通过alignContent参数实现位置的相对移动,类型为Alignment。子元素在容器内的对齐方式有9种方式:
(4) 实现列表侧滑:ListItem的
swipeAction属性可用于实现列表项的左右滑动功能。swipeAction属性方法初始化时有必填参数SwipeActionOptions,其中,start参数表示设置列表项右滑时起始端滑出的组件,end参数表示设置列表项左滑时尾端滑出的组件。
// 任务类 @Observed 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 }) } // 任务完成样式 @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[] = [] build(){ Column({ space: 10 }){ // 任务进度卡片 TaskStatistics({ finishTask: this.finishTask, totalTask: this.totalTask }) // 任务列表 TaskList({ finishTask: $finishTask, totalTask: $totalTask }) }.width('100%').height('100%') .backgroundColor('#F1F2F3') } } @Component struct TaskStatistics{ @Prop finishTask: number @Prop totalTask: number build(){ 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(24).fontColor(Color.Blue) Text(' / ' + this.totalTask.toString()).fontSize(24) } } }.card().margin({ top:20, bottom: 10 }).justifyContent(FlexAlign.SpaceEvenly) } } @Component struct TaskList{ @Link totalTask: number @Link finishTask: number @State tasks: Task[] = [] handleTaskChange() { this.totalTask = this.tasks.length this.finishTask = this.tasks.filter(item => item.finished).length } build(){ Column(){ // 新增任务按钮 Button('新增任务').width(200) .onClick(() => { this.tasks.push(new Task()) this.handleTaskChange() }) // 任务列表 List({ space: 10 }){ ForEach(this.tasks,(item, index) => { ListItem(){ TaskItem({ item: item, onTaskChange: this.handleTaskChange.bind(this)}) }.swipeAction({ end: this.DeleteButton(index)}) }) }.width('100%').alignListItem(ListItemAlign.Center) .layoutWeight(1) } } @Builder DeleteButton(index: number){ Button(){ Image($r('app.media.delete')).fillColor(Color.White).width(20) }.width(40).height(40).type(ButtonType.Circle) .backgroundColor(Color.White).margin(5) .onClick(() => { this.tasks.splice(index, 1) this.handleTaskChange() }) } } @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(val => { this.item.finished = val this.onTaskChange() }) }.card().justifyContent(FlexAlign.SpaceBetween) } }
二、页面路由
页面路由指在应用程序中实现不同页面之间的跳转和数据传递。 这些页面存储在页面栈中,页面栈的最大容量上限为32个页面,使用router.clear()方法可以清空页面栈,释放内存。
1、跳转模式
Router模块提供了两种跳转模式,分别是:
-
router.pushUrl():目标页面不会替换当前页,而是压入页面栈。这样可以保留当前页的状态,并且可以通过返回键或者调用router.back()方法返回到当前页。
-
router.replaceUrl():目标页面会替换当前页,并销毁当前页。这样可以释放当前页的资源,并且无法返回到当前页。
2、实例模式
Router模块提供了两种实例模式,分别是:
-
Standard:多实例模式,也是默认情况下的跳转模式。目标页面会被添加到页面栈顶,无论栈中是否存在相同url的页面。
-
Single:单实例模式。如果目标页面的url已经存在于页面栈中,则会将离栈顶最近的同url页面移动到栈顶,该页面成为新建页。如果目标页面的url在页面栈中不存在同url页面,则按照默认的多实例模式进行跳转。
3、使用方法
(1)首先导入HarmonyOS提供的Router模块:
import router from '@ohos.router';
(2)然后利用router实现跳转、返回等操作:
跳转以pushUrl为例:
100001 | 内部错误,可能是渲染失败 |
100002 | 路由地址错误 |
10003 | 路由栈中页面超过32 |
要获取传递过来的参数,可通过getParams()方法。
返回上一页通过router.back(), 如果要返回指定页可通过设置url参数实现,同时可携带参数。
注:为了实现页面跳转我们需要在resources>base>profile中的main_pages.json中进行页面配置。也可在创建页面时,选择new Page,可自动配置。
返回前弹窗警告: router.showAlertBeforeBackPage()。
练习:
import router from '@ohos.router' class RouterInfo{ // 页面路径 url: string // 页面标题 title: string constructor(url: string, title: string) { this.url = url this.title = title } } @Entry @Component struct Index { @State message: string = '页面列表' private routers: RouterInfo[] = [ new RouterInfo('pages/Day1','图片查看案例'), new RouterInfo('pages/Day1.2','商品列表案例'), new RouterInfo('pages/Day2','任务列表案例') ] build() { Column() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) .height(80) List({space: 15}){ ForEach(this.routers,(router) => { ListItem(){ this.RouterItem(router) } }) }.layoutWeight(1).alignListItem(ListItemAlign.Center) .width('100%') } .width('100%') .height('100%') } @Builder RouterItem(r: RouterInfo){ Row(){ Text(r.title).fontSize(20).fontColor(Color.White) }.width('90%').padding(12).backgroundColor(Color.Blue) .justifyContent(FlexAlign.Center) .onClick(() => { router.pushUrl( { url: r.url }, router.RouterMode.Single, err => { if (err) { console.log(`路由失败,errCode:${err.code} errMsg:${err.message}`) } } ) }) } }