HarmonyOS NEXT智能终端应用开发 课件 第4章 UI状态管理_第1页
HarmonyOS NEXT智能终端应用开发 课件 第4章 UI状态管理_第2页
HarmonyOS NEXT智能终端应用开发 课件 第4章 UI状态管理_第3页
HarmonyOS NEXT智能终端应用开发 课件 第4章 UI状态管理_第4页
HarmonyOS NEXT智能终端应用开发 课件 第4章 UI状态管理_第5页
已阅读5页,还剩82页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第4章UI状态管理UI界面都是由组件为基本单元构建的,UI界面也是程序与用户进行交互的桥梁。这就要求当程序有信息需要展示给用户时,必须能够改变UI组件状态并将信息展示给用户。鸿蒙提供了完整的对组件的状态进行管理的机制。目录4.1状态管理概述4.2状态管理V14.3状态管理V24.4案例:基于MVVM模式的备忘录(V1版)4.5练习:基于MVVM模式的备忘录(V2版)4.1状态管理概述在鸿蒙的UI编程框架中,UI是程序运行结果的展示:程序构建了UI界面,在程序运行过程中程序状态发生改变,也就是数据发生改变时,将引发UI的重新渲染,进而将结果展示给用户。在ArkUI中这种机制统称为状态管理机制。如图。上图直观的描述了程序状态与UI组件的关系:程序状态的改变将引发UI的重新渲染,进而将程序结果在UI上展示出来;同时,对UI组件的事件处理将导致程序状态的改变。如此重复。为了管理UI状态,鸿蒙将UI中声明的属性划分为两类:使用特定装饰器装饰的称为状态变量的变量;未使用装饰器装饰的普通变量。只有通过装饰器的状态变量的更新才会引发UI重新渲染。随着鸿蒙的进化,鸿蒙的状态管理机制包括V1版本和V2版本。本章将V1本本和V2版本都做介绍。特别强调,V1状态管理机制和V2管理机制不可混用。4.2状态管理V1ArkUI状态管理V1提供了多种装饰器,通过使用这些装饰器,状态变量不仅可以观察在组件内的改变,还可以在不同组件层级间传递,比如父子组件、跨组件层级,也可以观察全局范围内的变化。下面对常用的状态装饰器的使用进行介绍。4.2.1@State:组件状态变量使用@State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就可以触发其直接绑定UI组件的刷新。当状态改变时,UI会发生对应的渲染改变。@State装饰的变量是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化。初始化也可选择使用命名参数机制从父组件完成初始化。看一个例子。这个例子在自定义组件中包含两个使用@State装饰的变量,可以通过按钮的点击事件修改它们的值,然后将修改的结果在UI组件上显示出来。为此,新建名称为ch0401的程序工程,然后,修改ets/pages/Index.ets为如下内容:classModel{publicvalue:string;constructor(value:string){this.value=value;}}

@ComponentstructMyComponent{@Statetitle:Model=newModel('HelloWorld');@Statecount:number=0;increaseBy:number=1;

build(){Column(){Text(`${this.title.value}`).width('100%').margin(10).fontSize($r('app.float.page_text_font_size')).textAlign(TextAlign.Center)Button(`Clicktochangetitle`).onClick(()=>{//@State变量的更新将触发上面的Text组件内容更新this.title.value=this.title.value==='HelloArkUI'?'HelloWorld':'HelloArkUI';}).width('100%').margin(10)

Button(`Clicktoincreasecount=${this.count}`).onClick(()=>{//@State变量的更新将触发该Button组件的内容更新this.count+=this.increaseBy;}).width('100%').margin(10)}}}

@Entry@ComponentstructEntryComponent{build(){Column(){//此处指定的参数都将在初始渲染时覆盖本地定义的默认值//并不是所有的参数都需要从父组件初始化MyComponent({count:1,increaseBy:2}).width('100%')Line().width('95%').height(5).backgroundColor('#FFFF00').margin(10)MyComponent({title:newModel('HelloWorld2'),count:7}).width('100%')}}}因为title和count是@State变量,因此,当它们的值发生变化时,将导致使用它们的UI组件被重新渲染。4.2.2@Prop:父子数据单向同步在子组件中可以定义使用@Prop装饰的变量,这个变量可以接受父组件状态变量的值并建立单向的同步关系,也就是,可以从父组件获取值,并且跟随父组件的值的变化而变化。@Prop变量允许在子组件中修改,但在子组件中修改后的值不会同步回父组件。看一个例子。在这个例子中,子组件接受父组件的状态变量的值,然后在子组件中改变@Prop变量的值。由于@Prop变量的时单向的,因此,改变后的值不会同步回父组件中。为此,在ets/pages目录下新建一个名称为TProp.ets的文件,修改其内容为如下代码:classBook{publictitle:string;publicpages:number;publicreadIt:boolean=false;

constructor(title:string,pages:number){this.title=title;this.pages=pages;}}

@ComponentstructReaderComp{@Propbook:Book=newBook("",0);

build(){Column(){Text(this.book.title).textAlign(TextAlign.Center).fontSize(25)Text(`...has${this.book.pages}pages!`).textAlign(TextAlign.Center).fontSize(25)Text(`...${this.book.readIt?"Ihaveread":'Ihavenotreadit'}`).textAlign(TextAlign.Center).fontSize(25).onClick(()=>this.book.readIt=true)}}}

@Entry@ComponentstructLibrary{@Statebook:Book=newBook('100secretsofC++',765);

build(){Column(){ReaderComp({book:this.book})Line().width('95%').height(5).backgroundColor('#FF0000').margin(10)ReaderComp({book:this.book})}}}子组件可以获取父组件的book的值,进而显示在Text组件上。4.2.3@Link:父子数据双向同步子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定,也就是说,父组件对数据的修改将自动同步到组组件中,子组件对数据的修改也将自动同步到父组件中。注意,@Link的使用限制:其一,@Link装饰器不能在@Entry装饰的自定义组件中使用;其二,@Link装饰的变量禁止本地初始化,否则编译期会报错。看一个例子。这个示例中,点击主界面上的“ParentView:SetGreenButton”,可以从父组件将变化同步给子组件,子组件也会发生对应的刷新;点击子组件GreenButton按钮,子组件会将变化同步给父组件,进而改变按钮的宽度。代码如下:classGreenButtonState{width:number=0;constructor(width:number){this.width=width;}}

@ComponentstructGreenButton{@LinkgreenButtonState:GreenButtonState;@LinkblueButtonWidth:number;

build(){Button('GreenButton').width(this.greenButtonState.width).height(40).fontSize(20).backgroundColor('#64bb5c').fontColor('#FFFFFF,90%').onClick(()=>{if(this.blueButtonWidth<600){this.blueButtonWidth+=60;}else{this.blueButtonWidth=360;}})}}

@Entry@ComponentstructAContainer{@StategreenButtonState:GreenButtonState=newGreenButtonState(180);@StateblueButtonWidth:number=360;

build(){Column(){//class类型从父组件@State向子组件@Link数据同步Button('ParentView:SetGreenButton').width(this.blueButtonWidth).height(40).fontSize(20).margin(12).fontColor('#FFFFFF,90%').onClick(()=>{this.greenButtonState.width=(this.greenButtonState.width<700)?this.greenButtonState.width+100:100;})//初始化子组件的@Link变量GreenButton({greenButtonState:this.greenButtonState,blueButtonWidth:this.blueButtonWidth}).margin(12)}}}在这个例子中,使子组件的greenButtonState与父组件的greenButtonState建立了双向关联;子组件的blueButtonWidth与父组件的blueButtonWidth建立了双向关联。4.2.4@Provide和@Consume:与后代组件双向同步@Provide和@Consume装饰器应用于祖先组件与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于@Link的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。@Provide装饰的变量是在祖先组件中,可以理解为被“提供”给后代的状态变量。@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先组件提供的变量。@Provide和@Consume通过在祖先组件和后代组件之间使用相同的变量名或者相同的变量别名进行绑定。看一个例子。这个示例是与后代组件双向同步状态@Provide和@Consume场景。当分别点击ToDo和ToDoItem组件内Button时,count的更改会双向同步在ToDo和ToDoItem中。代码如下:@ComponentstructToDoItem{//@Consume装饰的变量通过相同的属性名绑定其祖先组件ToDo内的@Provide变量@Consumecount:number;

build(){Column(){Text(`count(${this.count})`)Button(`count(${this.count}),count+1`).onClick(()=>this.count+=1)}.width('50%')}}

@ComponentstructToDoList{build(){Row({space:5}){ToDoItem()ToDoItem()}}}

@ComponentstructToDoDemo{build(){ToDoList()}}

@Entry@ComponentstructToDo{//@Provide装饰的变量祖先组件ToDo提供其后代组件@Providecount:number=0;

build(){Column(){Button(`count(${this.count}),count+1`).width('100%').onClick(()=>this.count+=1)Line().width('95%').height(5).backgroundColor('#FF0000').margin(10)ToDoDemo()}}}无论在祖先组件,或者在后代组件中对count变量的修改,都将进行双向同步。4.2.5@Observed和@ObjectLink:观察嵌套类对象属性变化前面介绍的装饰器,包括@State、@Prop、@Link、@Provide和@Consume装饰器,都只能观察到数据第一层的变化,也就是数据变量本身的变化,而不能观察到类对象属性数据的变化。@Observed和@ObjectLink可以观察到类对象属性的变化:@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步,也就是,子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。看一个例子。这个例子使用@Observed装饰器装饰了Info类,然后,在子组件ObjectLinkChild中使用@ObjectLink装饰了Info类型的testNum属性,并且该变量绑定了父组件的Info类的pNum属性,因此,在父组件或者子组件中对绑定变量的属性修改都将在其他组件中进行同步更新。代码如下:@ObservedclassInfo{publicinfo:number=0;

constructor(info:number){=info;}}

@ComponentstructObjectLinkChild{@ObjectLinktestNum:Info;

build(){Text(`ObjectLinkChildtestNum${this.testN}`).onClick(()=>{//可以对ObjectLink装饰对象的属性赋值this.testN=47;})}}

@Entry@ComponentstructParent{@StatepNum:Info=newInfo(1);

build(){Column(){Text(`ParentpNum${this.pN}`).onClick(()=>{this.pN+=1;})

ObjectLinkChild({testNum:this.pNum})}}}4.2.6LocalStorage:页面状态数据存储LocalStorage是页面级的UI状态存储,它是ArkTS为构建页面级别状态变量提供存储的内存“数据库”,通过结合与LocalStorage相关的装饰器@LocalStorageProp和@LocalStorageLink,可以完成页面级别的数据存储和共享:被@LocalStorageProp装饰的变量与LocalStorage中给定属性建立单向同步关系;被@LocalStorageLink装饰的变量与LocalStorage中给定属性建立双向同步关系。在LocalStorage中存储的数据是以“名/值”对的形式存在的。看一个例子。这个例子首先使用构造函数创建LocalStorage实例storage,然后,使用@Entry装饰器将storage添加到Parent顶层组件中,之后,再使用@LocalStorageLink或者@LocalStorageProp绑定LocalStorage对给定的属性,建立双向/单向数据同步。代码如下:classData{code:number;constructor(code:number){this.code=code;}}

//创建新实例并使用给定对象初始化letpara:Record<string,number>={'PropA':47};letstorage:LocalStorage=newLocalStorage(para);storage.setOrCreate('PropB',newData(50));

@ComponentstructLSChild{//@LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@LocalStorageProp('PropA')childLinkNumber:number=1;//@LocalStorageProp变量装饰器与LocalStorage中的'PropB'属性建立单向绑定@LocalStorageProp('PropB')childLinkObject:Data=newData(0);

build(){Column({space:15}){Button(`ChildfromLocalStorage${this.childLinkNumber}`)//更改不会同步至LocalStorage中的'PropA'.onClick(()=>{this.childLinkNumber+=1;})

Button(`ChildfromLocalStorage${this.childLinkObject.code}`)//更改不会同步至LocalStorage中的'PropB'.onClick(()=>{this.childLinkObject.code+=1;})}}}//使LocalStorage可从@Component组件访问@Entry(storage)@ComponentstructLSParent{//@LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定@LocalStorageLink('PropA')parentLinkNumber:number=1;//@LocalStorageLink变量装饰器与LocalStorage中的'PropB'属性建立双向绑定@LocalStorageLink('PropB')parentLinkObject:Data=newData(0);

build(){Column({space:15}){Button(`ParentfromLocalStorage${this.parentLinkNumber}`)//更改将同步至LocalStorage中的'PropA'.onClick(()=>{this.parentLinkNumber+=1;})

Button(`ParentfromLocalStorage${this.parentLinkObject.code}`)//更改将同步至LocalStorage中的'PropB'.onClick(()=>{this.parentLinkObject.code+=1;})//@Component子组件自动获得对ParentLocalStorage实例的访问权限。LSChild()}}}点击各个按钮可以发现,对于单向绑定,只会修改组件中的数据,而不会修改LocalStorage中的数据。但是,对于双向绑定,则在修改组件数据的同时,也会将修改同步到LoacalStorage中。4.2.7AppStorage:程序全局的UI状态存储AppStorage是与应用进程绑定的全局UI状态存储容器,由UI框架在应用程序启动时创建,为应用程序的UI状态数据提供中央存储,也就是说,在应用程序中可以直接使用AppStorage变量存储和访问数据。AppStorage存储的UI状态数据是应用级的全局状态共享。在AppStorage中存储的数据是以“名/值”对形式存在的。可以使用@StorageProp和@StorageLink与UI进行数据同步。看一个例子。这个例子使用@StorageLink装饰器与AppStorage配合使用,正如@LocalStorageLink与LocalStorage配合使用一样。此装饰器使用AppStorage中的属性创建双向数据同步。代码如下:classData{code:number;constructor(code:number){this.code=code;}}

AppStorage.setOrCreate('PropA',47);AppStorage.setOrCreate('PropB',newData(48));letstorage=newLocalStorage();storage.setOrCreate('LinkA',99);storage.setOrCreate('LinkB',newData(100));

@Entry(storage)@ComponentstructIndex{@StorageLink('PropA')storageLink:number=1;@StorageLink('PropB')storageLinkObject:Data=newData(1);@LocalStorageLink('LinkA')localStorageLink:number=1;@LocalStorageLink('LinkB')localStorageLinkObject:Data=newData(1);

build(){Column({space:20}){Text(`FromAppStorage${this.storageLink}`).onClick(()=>{this.storageLink+=1;})

Text(`FromAppStorage${this.storageLinkObject.code}`).onClick(()=>{this.storageLinkObject.code+=1;})

Text(`FromLocalStorage${this.localStorageLink}`).onClick(()=>{this.localStorageLink+=1;})

Text(`FromLocalStorage${this.localStorageLinkObject.code}`).onClick(()=>{this.localStorageLinkObject.code+=1;})}}}AppStorage是由ArkTS创建的全局变量,用于存储应用程序全局的数据状态。在这个例子中,程序存储了两个数据状态,并通过@StorageLink双向绑定到UI中。4.2.8PersistenceStorage:持久化存储UI状态数据PersistentStorage将选定的AppStorage属性保留在设备磁盘上,PersistentStorage和AppStorage中的属性建立双向同步。UI和业务逻辑不直接访问PersistentStorage中的属性,所有属性访问都是对AppStorage的访问,AppStorage中的更改会自动同步到PersistentStorage。看一个例子。代码如下:PersistentStorage.persistProp('aProp',10);

@Entry@ComponentstructTPersistenceIndex{@Statemessage:string='HelloWorld';@StorageLink('aProp')aProp:number=1000;

build(){Row(){Column(){Text(this.message).fontSize(20)//应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果Text(`${this.aProp}`).fontSize(20).onClick(()=>{this.aProp+=1;})}}}}代码:PersistentStorage.persistProp('aProp',10);这条语句首先检查该应用程序在磁盘中是否存在名称为“aProp”的属性,如果存在,则将数据读取到AppStorage中,否则,将数据写入磁盘并同时读取到AppStorage中。在@Entry组件中,使用语句:

@StorageLink('aProp')aProp:number=1000;将“aProp”属性与变量aProp进行双向绑定,因此,后续对变量aProp的修改将持久化到磁盘中。4.2.9Environment:查询设备的运行环境Environment是ArkUI框架在应用程序启动时创建的单例对象,它为AppStorage提供了一系列描述应用程序运行状态的属性。Environment的所有属性都是不可变的。应用程序如果需要了解设备的环境参数,以此来作出不同的场景判断,比如多语言,深浅色模式等,则可以使用Environment进行设备环境查询。Environment包括如表所示的内置参数。序号键数据类型值及其描述1accessibilityEnabledboolean获取无障碍屏幕读取是否启用2colorModeColorMode色彩模型类型:选项为ColorMode.LIGHT:浅色,ColorMode.DARK:深色3fontScalenumber字体大小比例。开发者需要配置configuration使fontScale跟随系统变化4fontWeightScalenumber字体粗细程度5layoutDirectionLayoutDirection布局方向类型:包括LayoutDirection.LTR:从左到右,LayoutDirection.RTL:从右到左6languageCodestring当前系统语言值,取值必须为小写字母,例如zh例如,下面的例子将显示系统当前的语言值。//将设备languageCode存入AppStorage中Environment.envProp('languageCode','en');

@Entry@ComponentstructIndex{@StorageProp('languageCode')languageCode:string='en';

build(){Row(){Column(){//输出当前设备的languageCodeText(this.languageCode)}}}}4.2.10$$语法:系统组件双向同步$$运算符为系统组件提供TS变量的引用,使得TS变量和系统组件的内部状态保持同步。不同的系统组件内部状态取决于组件,例如,TextInput组件的内部状态是text参数。也就是,可以使用$$语法绑定TextInput组件的text属性。当前$$语法支持的组件及其属性如表所示。序号组件属性序号组件属性1Checkboxselect2DatePicker selected3CheckboxGroup selectAll4TimePicker selected5MenuItem selected6Panelmode7Radiochecked8Ratingrating9Searchvalue10SideBarContainershowSideBar11Slidervalue12Stepperindex13Swiperindex14Tabsindex15TextAreatext16TextInputtext17TextPickerselected、value18ToggleisOn19AlphabetIndexerselected20Selectselected、value21BindSheetisShow22BindContentCoverisShow23Refreshrefreshing24GridItemselected25ListItemselected

下面举一个使用$$语法进行系统组件属性双向绑定的例子。代码如下:@Entry@ComponentstructTextInputExample{@Statetext:string=''

build(){Column({space:20}){Text(this.text)TextInput({text:$$this.text,placeholder:'inputyourword...'}).placeholderColor(Color.Grey).placeholderFont({size:14,weight:400}).caretColor(Color.Blue).width(300)}.width('100%').height('100%').justifyContent(FlexAlign.Center)}}这个程序使用$$语法:

TextInput({text:$$this.text,placeholder:'inputyourword...'})将TextInput组件的text属性绑定到@Entry组件的@State变量text上,因此,当TextInput的text属性发生变化时,将同步更新@Entry组件的text属性。4.3状态管理V2上一节介绍的V1版本状态管理装饰器可以观察以及同步数据的变化,但是V1状态管理装饰器存在下列限制:其一,状态变量不能独立于UI存在;其二,只能感知对象属性第一层的变化,无法做到深度观测和深度监听;其三,在更改对象中属性以及更改数组中元素的场景下存在冗余更新的问题;其四,装饰器间配合使用限制多,不易用。因此,ArkUI提出了状态管理V2版本。状态管理V2将观察能力增强到数据本身,数据本身就是可观察的,更改数据会触发相应的视图的更新。相较于状态管理V1,状态管理V2有如下优点:其一,状态变量独立于UI,更改数据会触发相应视图的更新;其二,支持对象的深度观测和深度监听;其三,支持对象中属性级精准更新及数组中元素的最小化更新;其四,装饰器易用性高、拓展性强。本节对状态管理V2进行介绍。4.3.1@ComponentV2:支持V2的自定义组件为了在自定义组件中使用V2装饰器,开发者需要使用@ComponentV2装饰器装饰自定义组件,也就是,@ComponentV2主要配合状态管理V2使用。特别强调:状态管理V1和状态管理V2不要混用!4.3.2@ObservedV2和@Trace:类属性变化观测可以使用@ObservedV2装饰器和@Trace装饰器装饰类以及类中的属性,进而对类对象中属性的进行观测。@ObservedV2和@Trace提供了对嵌套类对象属性变化直接观测的能力,是状态管理V2中相对核心的能力之一。看下面的例子。这个例子中,Farther类嵌套了Son类的属性。由于对Son类使用了@ObservedV2装饰器进行了装饰,同时,对于Son中需要被观察的属性age使用@Trace装饰器进行装饰,因此,可以观察到Son类对象的被装饰的age属性的变化。代码如下:@ObservedV2classSon{@Traceage:number=100;}

classFather{son:Son=newSon();}

@Entry@ComponentV2structV2ObservedV2Trace{father:Father=newFather();

build(){Column(){//当点击改变age时,Text组件会刷新Text(`${this.father.son.age}`).width('100%').textAlign(TextAlign.Center).fontSize(30).margin(50).onClick(()=>{this.father.son.age++;})}}}4.3.2@Local:组件变量状态观察使用@Local装饰器装饰组件的变量,为了实现对@ComponentV2装饰的自定义组件中变量变化的观测,也就是,当被@Local装饰的变量变化时,会刷新使用该变量的组件。看下面的例子。这个例子使用@Local装饰器装饰几个简单类型的变量,当修改被@Local装饰的简单变量时,系统将刷新相应的文本Text组件。完整代码如下:@Entry@ComponentV2structV2Local{@Localcount:number=0;@Localmessage:string="Hello";@Localflag:boolean=false;build(){Column(){Text(`${this.count}`).fontSize(25).margin(20)Text(`${this.message}`).fontSize(25)Text(`${this.flag}`).fontSize(25)Button("changeLocal").onClick(()=>{//当@Local装饰简单类型时,能够观测到对变量的赋值this.count++;this.message+="World";this.flag=!this.flag;})}}}需要强调,@Local仅限于观测被装饰的变量本身,也就是,当装饰简单类型时,能够观测到对变量的赋值;当装饰对象类型时,仅能观测到对对象整体的赋值;当装饰数组类型时,能观测到数组整体以及数组元素项的变化。对于嵌套的类及其属性的观测,可以使用上一节介绍的@ObservedV2和@Trace装饰器。4.3.3@Param和@Required:接受外部的参数输入可以使用@Param装饰器装饰组件的变量,使之接受外部的参数输入,同时,被@Param装饰的变量能够在初始化自定义组件时从外部传入,也就是,当数据源也是状态变量时,数据源的修改会同步给@Param,但是,ArkUI规定:不允许在组件内部直接修改被@Param装饰的变量。@Required装饰器与@Param装饰器配合使用,用于强制规定使用@Param装饰的变量必须从父组件初始化,看一个例子。这个程序在入口组件中使用@Local装饰了几个变量,然后,在构建子组件时,将这几个变量传递给子组件中使用@Param装饰的变量:因为使用了父组件中的@Local变量初始化子组件的@Param变量,因此,父组件对变量的修改将被子组件观测到,所以,修改的结果值也将在子组件中被更新。完整的代码如下:@Entry@ComponentV2structV2ParamIndex{@Localcount:number=0;@Localmessage:string="Hello";@Localflag:boolean=false;build(){Column(){Text(`Local${this.count}`).fontSize(25).margin({top:20})Text(`Local${this.message}`).fontSize(25)Text(`Local${this.flag}`).fontSize(25)Button("changeLocal").margin({bottom:40}).onClick(()=>{//对数据源的更改会同步给子组件this.count++;this.message+="World";this.flag=!this.flag;})

V2ParamChild({count:this.count,message:this.message,flag:this.flag})}}}

@ComponentV2structV2ParamChild{@Require@Paramcount:number;@Require@Parammessage:string;@Require@Paramflag:boolean;build(){Column(){Text(`Param${this.count}`).fontSize(25)Text(`Param${this.message}`).fontSize(25)Text(`Param${this.flag}`).fontSize(25)}}}4.3.4@Once可以使用@Once装饰器搭配@Param装饰器使用,使子组件的变量仅从外部初始化一次、不接受后续同步变化。@Once与@Param搭配使用时,可以在本地修改@Param变量的值。由于@Once的使用比较简单,这里不再举例。4.3.5@Event:子组件参数与父组件的双向同步机制由于子组件不能修改@Param变量的值,但是有时子组件又确实需要更新@Param变量的值,这时可以使用@Event装饰器:在子组件中使用@Event装饰一个回调方法,要求父组件在构建子组件时需要传入一个用于更新数据源的回调方法,在子组件需要修改@Param参数的值时,调用这个父组件传递的回调函数即可。看一个例子。这个例子在子组件中使用@Event更改父组件中变量,由于该变量作为子组件@Param变量的数据源,因此该变化会同步回子组件的@Param变量。完整的代码如下:@Entry@ComponentV2structV2EventIndex{@Localtitle:string="TitleOne";@LocalfontColor:Color=Color.Red;

build(){Column(){V2EventChild({title:this.title,fontColor:this.fontColor,changeFactory:(type:number)=>{if(type==1){this.title="TitleOne";this.fontColor=Color.Red;}elseif(type==2){this.title="TitleTwo";this.fontColor=Color.Green;}}})}}}

@ComponentV2structV2EventChild{@Paramtitle:string='';@ParamfontColor:Color=Color.Black;@EventchangeFactory:(x:number)=>void=(x:number)=>{};

build(){Column(){Text(`${this.title}`).fontSize(25).margin(20).width('100%').textAlign(TextAlign.Center).fontColor(this.fontColor)Button("changetoTitleTwo").width('100%').margin(20).onClick(()=>{this.changeFactory(2);})Button("changetoTitleOne").width('100%').margin(20).onClick(()=>{this.changeFactory(1);})}}}父组件在构建子组件时,为子组件的@Event变量传递了一个回调方法,当点击子组件的按钮时这个方法将被调用,从而修改了父组件的@Local变量title和fontColor的值,并进一步通过@Param传递到子组件中,因此,将导致子组件的文本组件被刷新。4.3.6@Provider和@Consumer:跨组件双向同步@Provider和@Consumer用于跨组件层级数据双向同步。@Provider,也就是数据提的供方,其所有的子组件都可以通过@Consumer绑定相同的key来获取@Provider提供的数据。@Consumer,也就是数据的消费方,可以通过绑定同样的key获取其最近父节点的@Provider的数据,当查找不到@Provider的数据时,使用本地默认值。在使用@Provider和@Consumer建立父子组件数据双向同步时,可以为绑定的数据指定一个别名。@Provider的使用语法如下:@Provider(alias?:string)varName:varType=initValue其中的可选参数“alias?:string”就是为变量varName指定的别名。类似的,@Consumer的使用语法如下:@Consumer(alias?:string)varName:varType=initValue其中的可选参数“alias?:string”就是为变量varName指定的别名。下面看一个例子。这个例子中,V2ProviderConsumerParent和V2ProviderConsumerChild组件中的变量str分别通过@Provider和@Consumer装饰器建立了双向同步。点击V2ProviderConsumerParent中的Button,改变@Provider装饰的str,通知其对应的@Consumer,使其对应的UI刷新。点击V2ProviderConsumerChild中Button,改变@Consumer装饰的str,通知其对应的@Provider,使其对应的UI刷新。完整代码如下:@Entry@ComponentV2structV2ProviderConsumerParent{@Provider()str:string='hello';

build(){Column(){Button(this.str).width('100%').margin(20).onClick(()=>{this.str+='0';})V2ProviderConsumerChild()}}}

@ComponentV2structV2ProviderConsumerChild{//@Consumer装饰的属性str和V2ProviderConsumerParent组件中//@Provider装饰的属性str名称相同,因此建立了双向绑定关系@Consumer()str:string='world';

build(){Column(){Button(this.str).width('100%').margin(20).onClick(()=>{this.str+='0';})}}}4.3.7@Monitor:监听状态变量值的修改@Monitor装饰器用于监听状态变量修改。只有被状态变量装饰器@Local、@Param、@Provider、@Consumer、@Computed装饰的变量才能被@Monitor监听到。@Monitor可以与@ObservedV2结合使用,可以监听到被@Trace装饰的类属性的变化。看一个例子。这个例子使用@Monitor装饰器监听了被@Local装饰的变量,同时,也监听了使用@ObservedV2装饰的类中的被@Trace装饰的属性。当这些变量或属性发生改变时,将调用指定的回调函数。完整代码如下:@ObservedV2classV2MonitorInfo{@Tracejob:string="Teacher";age:number=25;

//job被@Trace装饰,能够监听变化@Monitor("job")onJobChange(monitor:IMonitor){console.log(`jobchangedfrom${monitor.value()?.before}to${monitor.value()?.now}`);}}

@Entry@ComponentV2structV2MonitorIndex{@Localmessage:string="HelloWorld";@Localname:string="Tom";@Localage:number=24;info:V2MonitorInfo=newV2MonitorInfo();

@Monitor("message","name")onStrChange(monitor:IMonitor){monitor.dirty.forEach((path:string)=>{console.log(`${path}changedfrom${monitor.value(path)?.before}to${monitor.value(path)?.now}`)})}

build(){Column(){Button("changestring").width('100%').margin(10).fontSize(25).onClick(()=>{this.message+=",你好,世界";="Jack";})Button("changejob").width('100%').margin(10).fontSize(25).onClick(()=>{.job="engineer";//能够触发onJobChange方法})}}}运行这个程序,在显示的结果界面中点击“changestring”按钮,将在Log标签栏显示变量被修改的信息,如图.从上面的例子可以看到,@Minitor装饰器用于装饰一个回调函数,当被监听的变量的值发生变化时,这个回调函数将被调用。这个回调函数需要一个IMonitor类型的参数用于表示被修改的变量以及修改前后的值。IMonitor类型的属性及其含义如表所示。IMonitorValue<T>类型的属性及其含义如表。序号属性类型说明1dirtyArray<string>保存发生变化的属性名。2value<T>function(path?:string)该函数根据给定的属性参数,返回IMonitorValue<T>类型的结果。IMonitorValue<T>类型的属性及其含义如表4-2所示。序号属性类型说明1beforeT监听属性变化之前的值。2nowT监听属性变化之后的当前值。3pathstring监听的属性名。4.3.8AppStorageV2:应用程序全局数据存储器可以使用AppStorageV2存储应用程序全局状态变量数据。AppStorageV2是在应用UI启动时被创建的一个单例对象,它的目的是为了应用提供状态数据的中心存储,这些状态数据在应用级别通过唯一的键进行访问。在程序中要使用AppStorageV2存储,在程序的开始处需要使用如下语句导入模块:import{AppStorageV2}from'@kit.ArkUI';在导入了模块后,即可使用AppStorageV2的如下方法操作数据。1、connect:创建或获取储存的数据2、remove:删除指定key的储存数据3、keys:返回所有AppStorageV2中的key看一个例子。下面举一个例子说明AppStorageV2的使用。这个例子显示两个按钮,点击第一个按钮将修改变量的值:由于该变量已经通过connect()方法建立了与AppStorageV2的关联,因此数据将保存到AppStorageV2中;点击第二个按钮,从AppStorageV2中读取数据的key并显示出来。完整代码如下:import{AppStorageV2}from'@kit.ArkUI';

@ObservedV2exportclassSample{@Tracep1:number=0;p2:number=10;}

@Entry@ComponentV2structV2AppStorageV2{@Localhint:string='...';//在AppStorageV2中创建一个key为Sample的键值对//(如果存在,则返回AppStorageV2中的数据),并且和prop关联@Localprop:Sample=AppStorageV2.connect(Sample,()=>newSample())!;

build(){Column(){Button('保存一些数据').width('100%').margin(10).fontSize(20).fontWeight(FontWeight.Bold).onClick(()=>{p.p1+=100;p.p2+=1000;})Button(this.hint).width('100%').margin(10).fontSize(20).fontWeight(FontWeight.Bold).onClick(()=>{this.hint=p.p1+':'+AppStorageV2.keys()[0];})}.height('100%').width('100%')}}运行这个程序,点击第一个按钮保存一些数据,然后再点击第二个按钮显示数据及其key。由于AppStorageV2是全局的对象,因此,可用于多个页面之间的数据交换或通信。4.3.9PersistenceV2:持久化储存状态数据使用PersistenceV2存储持久化的数据,在应用重新启动时,可以获得上次关闭程序时存储的数据值。每个持久化数据,通过与之对应的key进行访问。在程序中要使用PersistenceV2持久化数据,在程序的开始处需要使用如下语句导入模块:import{PersistenceV2}from'@kit.ArkUI';在导入了模块后,即可使用PersistenceV2的如下方法操作数据。1、connect:创建或获取储存的数据2、remove:删除指定key的储存数据3、keys:返回所有PersistenceV2中的key4、save:手动持久化数据5、notifyOnError:响应序列化或反序列化失败的回调。PersistenceV2的使用与AppStorageV2的使用非常类似,只是AppStorageV2将数据存储在内存中,而PersistenceV2将数据持久化存储在磁盘上。4.3.10在V2中双向绑定系统组件的属性在V2中可使用“$$”绑定系统组件的属性,详细内容参见4.2.10节的介绍。因此,在使用状态管理V2的情况下,也可以完成系统组件的属性与状态变量的双向绑定。看一个简单的例子。完整代码如下:(这个例子与4.2.10节的例子类似,运行结果完全一致)@Entry@ComponentV2structV2SurpriseSurprise{@Localtext:string='';

build(){Column({space:20}){Text(this.text)TextInput({text:$$this.text,placeholder:'inputyourword...'}).placeholderColor(Color.Grey).placeholderFont({size:14,weight:400}).caretColor(Color.Blue).width(300)}.width('100%').height('100%').justifyContent(FlexAlign.Center)}}4.4案例:基于MVVM模式的备忘录(V1版)所谓备忘录程序,就是把一些重要的事情记录在程序中用于提醒自己,以便不会忘记需要处理的重要事情。4.4.1案例目标实现一个备忘录管理程序,以优雅的方式显示所有待办事项,同时可以删除已经完成的代办事项。完成后的程序运行首界面如图4-18所示。点击“全选”按钮,显示如图4-19所示的界面;点击“取消全选”按钮,邮件显示如图4-18所示的界面。在图4-18或者4-19中点击某个条目前面的对勾符号或者感叹号符号,会将响应的条目设置为已办或者代办,如图。4.4.2案例分析这个案例将使用MVVM模式来完成这个备忘录程序的编写。这里涉及到一个重要的概念:MVVM。MVVM将应用分为Model、View和ViewModel三个核心部分,实现数据、视图与逻辑的分离。通过这种模式,UI可以随着状态的变化自动更新,从而更加高效地管理数据和视图的绑定与更新。(1)Model:负责存储和管理应用的数据以及业务逻辑,不直接与用户界面交互。通常从后端接口获取数据,是应用程序的数据基础,确保数据的一致性和完整性。(2)View:负责用户界面展示数据并与用户交互,不包含任何业务逻辑。它通过绑定ViewModel层提供的数据来动态更新UI。(3)ViewModel:负责管理UI状态和交互逻辑。作为连接Model和View的桥梁,通常一个View对应一个ViewModel,ViewModel监控Model数据的变化,通知View更新UI,同时处理用户交互事件并转换为数据操作。ArkUI采用了Model-View-ViewModel(MVVM)架构模式。本章介绍的状态管理就是MVVM的具体实现:在ArkUI下,View就是一个个页面page或者UI组件;Model就是UI页面中存储数据的变量;ViewModel就是ArkUI中通过状态管理装饰器监视的各个数据Model。通过ArkUI的MVVM,开发者只用关注页面设计,而不去关注整个UI的刷新逻辑,数据的维护也无需开发者进行感知,而由状态变量自动更新完成,因此,使用ArkUI的MVVM,可以提高程序的开发效率。4.4.3案例实施采用MVVM方式实现这个案例程序。为此,首先新建名称为ch0402的鸿蒙程序工程,并根据MVVM模

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论