赞
踩
目录
1.创建自定义组件(@Entry、@Component装饰器)
2.自定义组件构建函数创建自定义组件(@Builder装饰器)
8.多层双向传递(@Provider+@Cosume装饰器)
9.嵌套对象或数组元素为对象的双向数据同步(@Observed+@ObjectLink装饰器)(补充)
本系列是windows系统下、采用ArkTS语言、ArkUI框架、deveco studio编译器学习纯鸿蒙软件研发,采用API version 9进行。本小节主要了解鸿蒙开发的各种装饰器(带有@符号具有特定功能的关键字),包括如何自定义组件、自定义组件构建函数、通过变量实现UI刷新、变量更改监听、自定义样式实现样式复用,单向、双向、多层双向参数传递,嵌套对象,多层嵌套对象,对象作为数组元素,嵌套对象作为数组元素,多层嵌套对象作为数组元素的数据同步。纯小白,一步步学习,记录一下过程便于查询。
ArkUI中附带了系统组件,但是很多场景下我们需要自定义组件来满足我们组合使用系统组件的布局、属性、方法的要求,或者复用自定义组件避免冗余代码,或者通过自定义组件实现变量改变驱动UI刷新等。
明确需自定义的组件布局和逻辑(可以先用系统组件结合起来布局一下,UI预览查看一下效果是否符合要求)>声明组件(含参需要声明接收参数的变量)>调用组件(可能需要传参)
其中自定义组件包括含参和不含参两种,含参的自定义组件可以通过组件内定义私有变量接收传参,调用时使用键值对方式键为私有变量名传递参数的方式,进行参数的定义和传递。
自定义组件的属性和事件可以在声明自定义组件时直接写在自定义组件中(一般这种属性和方法时是需要复用的),如上述的写法,也可以在调用自定义组件时进行链式编程(这种可以写除了复用外的属性和方法以外的)。
被state修饰的变量,变量值改变会驱动UI刷新,可以通过该方式实现一些效果。
实现效果
自定义组件构建函数->@Builder装饰器:使用函数实现自定义组件的效果。编写方式为通过Builder装饰器装饰函数,函数就可以编写组件形式的代码。
(1)无传参
(2)有传参:和函数传参方式相同,直接在小括号中加入参数即可。
(3)通过变量值改变驱动UI刷新和前面是相同的。
上述的方式只可以在组件内部调用函数实现自定义组件,这样多个组件均可以使用我们的自定义组件,那么需要将函数写到组件外部,加上function关键字,并且调用时不能加this关键字。实现如下。
上述通过修改被state装饰装饰的变量实现UI刷新的方式存在bug:如果界面中存在多处修改该变量的值,多处调用变量时,就会出现逻辑错误。
例如:在某个自定义组件的点击事件中修改了该变量的值并使用该变量值驱动UI刷新,之后代码多处使用了这个自定义组件,就会出现一处点击,多处出现点击效果的情况。点击button1修改text1文字,点击button2修改text2文字,可能会呈现点击button1,text1和text2文字均被修改的情况。
解决办法为:遵守参数传递规则传递参数
解决方式:参考下方的7.状态管理中的@State+@LInk
布局中无可避免对于部分组件我们可能使用相同的样式,也就是复用样式的情况,为避免冗余代码,可以抽取出相同的样式代码进行复用。
定义样式函数>使用@Styles装饰器装饰函数>在函数中编写样式代码>采用链式编程方式调用样式函数(不可以用this),同样的如果要多个组件中均使用该样式函数,同样需要添加function关键字,其他均相同。
注意:
注意:
不同场景下显示不同的样式:例如链接点击前、点击、点击后的样式,按钮点击前,短按,长按,点击完毕的样式。属性方法stateStyle
注意:
在声明式UI中,是以章台驱动视图更新的。状态是指驱动视图更新的数据(被装饰器标记的变量),视图是指基于UI描述(例如build函数内部描述)渲染得到的用户界面。用户通过与视图中的页面元素交互触发互动事件可以改变状态变量的值。状态变量的变化可以触发自带的监控事件对UI界面重新渲染。
状态管理分为组件间的状态管理和硬件状态管理,由各种各样的装饰器装饰变量。
单向数据传递:在父组件中被@State装饰的变量的值发生改变,传递给子组件,子组件的UI也会发生改变,但是子组件中@prop装饰的变量值发生改变,则不会影响父组件UI
双向数据传递:在父组件中被@State装饰的变量的值发生改变,传递给子组件,子组件的UI也会发生改变,子组件中@link装饰的变量值发生改变,父组件UI也会改变
★★★注意:①State装饰的变量必须初始化,不能为空。②且State支持Object、class、string、number、boolean、enum类型以及这些类型的数组,不支持any、union等类型。③嵌套类型(例如Object对象,如果具备一个Object属性,它的Obhject属性发生变化就无法监控到)以及数组中的对象属性)无法触发视图更新。
父子组件双向传值可以采用@State+@Link方式,但是对于多层传值,例如爷孙,或者中间间隔了很多代的数据双向传值时,可以采用@Provider+@Cosume装饰器传值。
注意:
多层双向传递变量名修改:可以采用定义别名的方式实现修改变量名的目的,但是别名需要相同,则可以达到同样的多层双向传递,使用代码示例如下:
- @Provide('test') num4:number=2
- @Consume('test') num7:number
之前的数据传递装饰器例如@State、@Link、@Prop、@Provider、@Cosume均不适用于嵌套对象或数组元素为对象,无法监听到嵌套对象或数组元素为对象的内部对象属性的改变,但是@Observed和@ObjectLink可以实现嵌套对象或数组元素为对象的双向数据同步。
注意:①凡是需要监控变化的嵌套对象数组元素为对象均需要使用@Observed装饰,除非对象内部嵌套的是对象本身。
②如果嵌套对象是封装的类,使用类的时候,会用嵌套的对象的属性作为参数,这个属性也是一个对象,这个对象作为参数无法被@ObjectLink装饰,则需要定义组件,将内部嵌套的对象传递,并且在定义的组件中使用变量接收,并用@ObjectLink装饰接收的变量。
使用示例:
(1)使用@State+@Link:除了对象中非嵌套的数据会被同步,其余均不会生效。
测试代码如下:
- //被嵌套对象
- export class FoodInfo{
- id:number=0;
- name:string='';
- img:Resource|null=null;
- category:number=0;
- kk:number=0;
- rl:number=0;
- yy:number=0;
- zf:number=0;
- }
- //嵌套对象
- import { FoodInfo } from './FoodInfo';
- //封装的类
- export class Food{
- price:number;
- unit:string;
- //嵌套了一个别的类
- foodInfo:FoodInfo;
- //嵌套了一个类本身
- food:Food;
- constructor(price:number,unit:string,foodInfo:FoodInfo,food?:Food){
- this.price=price;
- this.unit=unit;
- this.foodInfo=foodInfo;
- this.food=food;
- }
- }
- //使用Food测试:
- import PageTitle from '../view/PageTitle'
- import { Food } from '../ViewData/Food'
- @Entry
- @Component
- struct ObjectVariableWatch{
- @State food:Food=new Food(2,"元/斤",
- //1.修改嵌套的其他对象的属性
- {id:0,name:'番茄',kk:15,category:1,img:$r('app.media.tomato'),rl:0,yy:0,zf:0},
- //2.修改嵌套的对象本身的属性
- new Food(2,"元/斤",
- //修改多层嵌套对象的属性
- {id:0,name:'番茄',kk:15,category:1,img:$r('app.media.tomato'),rl:0,yy:0,zf:0}))
- build() {
- Row() {
- Column() {
- //如果数据是嵌套的对象,使用@State修改嵌套的内部对象的属性值,将不会发生变化。
- Test({food:$food})
- }
- }
- }
- }
- @Component
- struct Test{
- @Link food:Food
- build(){
- Row() {
- Column() {
- //标题
- PageTitle()
- Stack(){
- Image(this.food.foodInfo.img)
- .objectFit(ImageFit.Contain)
- Text(this.food.foodInfo.name)
- .fontSize(26).fontColor(0X3E3E3E)
- .width('90%').position({x:10,y:230})
- }.height(300)
- //测试
- Column({space:10}){
- //修改非嵌套属性:只有该处生效
- Row(){
- Circle({width:6,height:6}).margin({right:12}).fill('#ffaaaa')
- Text('价格').fontSize(16).fontColor(0X3E3E3E)
- Blank()
- Text(this.food.price+' '+this.food.unit).fontSize(16).fontColor(0X3E3E3E)
- }.onClick(()=>{
- this.food.price++;
- })
- //修改嵌套的其他对象的属性
- Row(){
- Circle({width:6,height:6}).margin({right:12}).fill('#ffaaaa')
- Text('热量').fontSize(16).fontColor(0X3E3E3E)
- Blank()
- Text(this.food.foodInfo.rl+' '+'千卡').fontSize(16).fontColor(0X3E3E3E)
- }.onClick(()=>{
- this.food.foodInfo.rl++;
- })
- //修改嵌套的对象本身的属性
- Row(){
- Circle({width:6,height:6}).margin({right:12}).fill('#ffaaaa')
- Text('嵌套对象价格').fontSize(16).fontColor(0X3E3E3E)
- Blank()
- Text(this.food.food.price+' '+this.food.food.unit).fontSize(16).fontColor(0X3E3E3E)
- }.onClick(()=>{
- this.food.food.price++;
- })
- //修改嵌套的对象内嵌套对象的属性
- Row(){
- Circle({width:6,height:6}).margin({right:12}).fill('#ffaaaa')
- Text('嵌套对象的嵌套对象的属性热量').fontSize(16).fontColor(0X3E3E3E)
- Blank()
- Text(this.food.food.foodInfo.rl+' '+'千卡').fontSize(16).fontColor(0X3E3E3E)
- }.onClick(()=>{
- this.food.food.foodInfo.rl++;
- })
- }.width('90%').margin({top:20,bottom:20})
- .padding(20)
- .borderRadius(10)
- .backgroundColor(Color.White)
- }
- .height('100%')
- .backgroundColor('#dedede')
- }
- }
- }
实现效果:除了对象中非嵌套的数据会被同步,其余均不会生效。
(2)使用@Observed+@ObjectLink装饰器实现:均可生效。
测试代码如下:该测试案例中实现了:嵌套对象,多层嵌套对象,对象作为数组元素,嵌套对象作为数组元素,多层嵌套对象作为数组元素的数据同步,均生效。
- //被嵌套对象
- @Observed//这个装饰器必须添加
- export class FoodInfoTest{
- id:number=0;
- name:string='';
- img:Resource|null=null;
- category:number=0;
- kk:number=0;
- rl:number=0;
- yy:number=0;
- zf:number=0;
-
- constructor(id:number,name:string,img:Resource|null, category:number, kk:number, rl:number, yy:number, zf:number) {
- this.id=id;
- this.name=name;
- this.img=img;
- this,category=category;
- this.kk=kk;
- this.rl=rl;
- this.yy=yy;
- this.zf=zf;
- }
- }
- //嵌套对象
- import { FoodInfoTest } from './FoodInfoTest';
- //封装的类
- @Observed//这个装饰器也必须加
- export class FoodTest{
- price:number;
- unit:string;
- //嵌套了一个别的类
- foodInfoTest:FoodInfoTest;
- //嵌套了一个类本身
- foodTest:FoodTest;
- constructor(price:number,unit:string,foodInfoTest:FoodInfoTest,foodTest?:FoodTest){
- this.price=price;
- this.unit=unit;
- this.foodInfoTest=foodInfoTest;
- this.foodTest=foodTest;
- }
- }
- //使用嵌套数据做测试
- import PageTitle from '../view/PageTitle'
- import { FoodInfoTest } from '../ViewData/FoodInfoTest'
- import { FoodTest } from '../ViewData/FoodTest'
- @Entry
- @Component
- struct ObjectVariableWatchTest{
- //数组元素是对象
- @State foodArray:Array<FoodInfoTest>=[new FoodInfoTest(0,"番茄",$r('app.media.tomato'),1,15,0,0,0)];
-
- //数组元素是嵌套对象
- @State foodObjectQTArray:Array<FoodTest>=[new FoodTest(2,"元/斤",
- //1.修改嵌套的其他对象的属性
- new FoodInfoTest(0,"番茄",$r('app.media.tomato'),1,15,0,0,0),
- //2.修改嵌套的对象本身的属性
- new FoodTest(2,"元/斤",
- //修改多层嵌套对象的属性
- new FoodInfoTest(0,"番茄",$r('app.media.tomato'),1,15,0,0,0)))];
-
-
- @State foodTest:FoodTest=new FoodTest(2,"元/斤",
- //1.修改嵌套的其他对象的属性
- new FoodInfoTest(0,"番茄",$r('app.media.tomato'),1,15,0,0,0),
- //2.修改嵌套的对象本身的属性
- new FoodTest(2,"元/斤",
- //修改多层嵌套对象的属性
- new FoodInfoTest(0,"番茄",$r('app.media.tomato'),1,15,0,0,0)))
- build() {
- Row() {
- Column() {
- //直接传递该对象即使使用@ObjectLink装饰也不会生效
- // Test({food:this.food})
- PageTitle()
- Stack(){
- Image(this.foodTest.foodInfoTest.img)
- .objectFit(ImageFit.Contain)
- Text(this.foodTest.foodInfoTest.name)
- .fontSize(26).fontColor(0X3E3E3E)
- .width('90%').position({x:10,y:230})
- }.height(300)
- Column({space:10}){
- //修改非嵌套属性:直接可以生效,无需做别的
- Row(){
- Circle({width:6,height:6}).margin({right:12}).fill('#ffaaaa')
- Text('价格').fontSize(16).fontColor(0X3E3E3E)
- Blank()
- Text(this.foodTest.price+' '+this.foodTest.unit).fontSize(16).fontColor(0X3E3E3E)
- }.onClick(()=>{
- this.foodTest.price++;
- })
- //修改嵌套的其他对象的属性:将嵌套的对象作为参数传递到自定义组件中:生效
- LineContent1({foodinfoqtyc:this.foodTest.foodInfoTest,name:'热量',unit:'千卡'})
- //修改嵌套的对象本身的属性:生效
- LineContent2({foodinfoqtbsyc:this.foodTest.foodTest})
- //修改嵌套的对象内嵌套对象的属性:生效
- LineContent1({foodinfoqtyc:this.foodTest.foodTest.foodInfoTest,name:'嵌套对象的嵌套对象的属性热量',unit:'千卡'})
- //数组元素为对象,对象的属性
- LineContent1({foodinfoqtyc:this.foodArray[0],name:'数组对象热量',unit:'千卡'})
- //数组元素为对象,对象的嵌套对象的属性
- LineContent1({foodinfoqtyc:this.foodObjectQTArray[0].foodInfoTest,name:'数组对象的嵌套对象热量',unit:'千卡'})
- //数组元素为对象,对象的嵌套对象的嵌套对象的属性
- LineContent1({foodinfoqtyc:this.foodObjectQTArray[0].foodTest.foodInfoTest,name:'数组对象的嵌套对象嵌套热量',unit:'千卡'})
- }.width('90%').margin({top:20,bottom:20})
- .padding(20)
- .borderRadius(10)
- .backgroundColor(Color.White)
-
- }
- }
- }
- }
-
- @Component
- struct LineContent1{
- @ObjectLink foodinfoqtyc:FoodInfoTest
- private name:string
- private unit:string
- build(){
- //修改嵌套的其他对象的属性
- Row(){
- Circle({width:6,height:6}).margin({right:12}).fill('#ffaaaa')
- Text(this.name).fontSize(16).fontColor(0X3E3E3E)
- Blank()
- Text(this.foodinfoqtyc.rl+' '+this.unit).fontSize(16).fontColor(0X3E3E3E)
- }.onClick(()=>{
- console.log(this.foodinfoqtyc.rl+"")
- this.foodinfoqtyc.rl++;
- console.log(this.foodinfoqtyc.rl+"")
- })
- }
- }
- @Component
- struct LineContent2{
- @ObjectLink foodinfoqtbsyc:FoodTest
- build(){
- //修改嵌套的其他对象的属性
- Row(){
- Circle({width:6,height:6}).margin({right:12}).fill('#ffaaaa')
- Text('嵌套对象价格').fontSize(16).fontColor(0X3E3E3E)
- Blank()
- Text(this.foodinfoqtbsyc.price+' '+this.foodinfoqtbsyc.unit).fontSize(16).fontColor(0X3E3E3E)
- }.onClick(()=>{
- this.foodinfoqtbsyc.price++;
- })
- }
- }
实现效果:
★★★★★★bug:如果嵌套对象内部的对象的属性数据同步不生效,可以检查是否出现了使用'{}'实例化对象而不是使用new关键字。例如上方嵌套对象按照写法1,其嵌套的FoodInfoTest无论是几层,均不会生效。按照写法二则均可生效,因为我们使用@Obversed装饰的是FoodInfoTest这个类。而不是'{}'括起来的数据。
写法一:错误写法:改写法无论赋值还是操作均不会出错,但是数据同步不会生效。
- @State foodTest1:FoodTest=new FoodTest(2,"元/斤",
- {id:0,name:"番茄",img:$r('app.media.tomato'),category:1,kk:15,rl:0,yy:0,zf:0},
- new FoodTest(2,"元/斤",
- {id:0,name:"番茄",img:$r('app.media.tomato'),category:1,kk:15,rl:0,yy:0,zf:0}));
写法二:正确写法
- @State foodTest:FoodTest=new FoodTest(2,"元/斤",
- new FoodInfoTest(0,"番茄",$r('app.media.tomato'),1,15,0,0,0),
- new FoodTest(2,"元/斤",
- new FoodInfoTest(0,"番茄",$r('app.media.tomato'),1,15,0,0,0)))
被@Watch装饰的状态变量(被装饰器修饰的变量:@state,@link,@prop,@provide、@consume等)一旦发生改变,就会通知我们实现一些业务。
注意:自定义组件、自定义样式等在不同文件中定义均不可以重名,因为组件、样式均是按照项目进行树状存储的,即使在不同文件中定义重名也会出错。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。