HarmonyOS鸿蒙学习基础篇 - 自界说组件(一)

打印 上一主题 下一主题

主题 837|帖子 837|积分 2511

媒介

    在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为体系组件,由开辟者界说的称为自界说组件。在举行 UI 界面开辟时,通常不是简单的将体系组件举行组合使用,而是必要思量代码可复用性、业务逻辑与UI分离,后续版本演进等因素。因此,将UI和部分业务逻辑封装成自界说组件是不可或缺的本领。
自界说组特点:


  • 可组合:允许开辟者组合使用体系组件、及其属性和方法。
  • 可重用:自界说组件可以被其他组件重用,并作为差别的实例在差别的父组件或容器中使用。
  • 数据驱动UI更新:通过状态变量的改变,来驱动UI的刷新
根本用法:

  1. @Component
  2. struct HelloComponent {
  3.   @State message: string = 'Hello, World!';
  4.   build() {
  5.     // HelloComponent自定义组件组合系统组件Row和Text
  6.     Row() {
  7.       Text(this.message)
  8.         .onClick(() => {
  9.           // 状态变量message的改变驱动UI刷新,UI从'Hello, World!'刷新为'Hello, ArkUI!'
  10.           this.message = 'Hello, ArkUI!';
  11.         })
  12.     }
  13.   }
  14. }
复制代码
  HelloComponent可以在其他自界说组件中的build()函数中多次创建,实现自界说组件的重用。
  
  1. @Entry
  2. @Component
  3. struct ParentComponent {
  4.   build() {
  5.     Column() {
  6.       Text('ArkUI message')
  7.       HelloComponent({ message: 'Hello, World!' });
  8.       Divider()
  9.       HelloComponent({ message: '你好!' });
  10.     }
  11.   }
  12. }
复制代码
  以上代码写入一个arkts文件中,并设置对应的字体样式,如下:
  
  1. @Component
  2. struct HelloComponent {
  3.   @State message: string = 'Hello, World!';
  4.   build() {
  5.     // HelloComponent自定义组件组合系统组件Row和Text
  6.     Row() {
  7.       Text(this.message)
  8.         .height(50)
  9.         .width(200)
  10.         .fontSize(30)
  11.         .onClick(() => {
  12.           // 状态变量message的改变驱动UI刷新,UI从'Hello, World!'刷新为'Hello, ArkUI!'
  13.           this.message = 'Hello, ArkUI!';
  14.         })
  15.     }
  16.   }
  17. }
  18. @Entry
  19. @Component
  20. struct ParentComponent {
  21.   build() {
  22.     Column() {
  23.       Text('ArkUI message')
  24.         .height(100)
  25.         .fontSize(50)
  26.         .fontWeight(FontWeight.Bold)
  27.       HelloComponent({ message: 'Hello, World!' });
  28.       Divider()
  29.       HelloComponent({ message: '你好!' });
  30.     }
  31.   }
  32. }
复制代码
  预览如下,点击“Hello,World!”或者“你好”都会改变为“Hello, ArkUI!”做到了自界说组件复用。
   


根本结构

1.struct

自界说组件基于struct实现,struct + 自界说组件名 + {...}的组合构成自界说组件,不能有继续关系。对于struct的实例化,可以省略new。
注意:自界说组件名、类名、函数名不能和体系组件名相同。
2.@Component

@Component装饰器仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的本领,必要实现build方法描述UI,一个struct只能被一个@Component装饰。
@Component
struct MyComponent {
}

注意:从API version 9开始,该装饰器支持在ArkTS卡片中使用。
3.build()函数

build()函数用于界说自界说组件的声明式UI描述,自界说组件必须界说build()函数。
@Component
struct MyComponent {
  build() {
  }
}
4.@Entry

@Entry装饰的自界说组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自界说组件。@Entry可以接受一个可选的LocalStorage的参数。
@Entry
@Component
struct MyComponent {
}
注意:从API version 9开始,该装饰器支持在ArkTS卡片中使用。

函数变量

自界说组件除了必须要实现build()函数外,还可以实现其他成员函数,成员函数具有以下束缚:

  • 不支持静态函数。
  • 成员函数的访问是私有的。
自界说组件可以包含成员变量,成员变量具有以下束缚:

  • 不支持静态成员变量。
  • 全部成员变量都是私有的,变量的访问规则与成员函数的访问规则相同。
  • 自界说组件的成员变量当地初始化有些是可选的,有些是必选的。具体是否必要当地初始化,是否必要从父组件通过参数通报初始化子组件的成员变量,请参考状态管理概述。
参数规定

从上文的示例中,我们已经了解到,可以在build方法或者@Builder装饰的函数里创建自界说组件,在创建自界说组件的过程中,根据装饰器的规则来初始化自界说组件的参数。
  1. @Component
  2. struct MyComponent {
  3.   private countDownFrom: number = 0;
  4.   private color: Color = Color.Blue;
  5.   build() {
  6.   }
  7. }
  8. @Entry
  9. @Component
  10. struct ParentComponent {
  11.   private someColor: Color = Color.Pink;
  12.   build() {
  13.     Column() {
  14.       // 创建MyComponent实例,并将创建MyComponent成员变量countDownFrom初始化为10,将成员变量color初始化为this.someColor
  15.       MyComponent({ countDownFrom: 10, color: this.someColor })
  16.     }
  17.   }
  18. }
复制代码


build()函数

全部声明在build()函数的语言,我们统称为UI描述,UI描述必要遵循以下规则:

  • @Entry装饰的自界说组件,其build()函数下的根节点唯一且须要,且必须为容器组件,其中ForEach克制作为根节点。@Component装饰的自界说组件,其build()函数下的根节点唯一且须要,可以为非容器组件,其中ForEach克制作为根节点。
  1. @Entry
  2. @Component
  3. struct MyComponent {
  4.   build() {
  5.     // 根节点唯一且必要,必须为容器组件
  6.     Row() {
  7.       ChildComponent()
  8.     }
  9.   }
  10. }
  11. @Component
  12. struct ChildComponent {
  13.   build() {
  14.     // 根节点唯一且必要,可为非容器组件
  15.     Image('test.jpg')
  16.   }
  17. }
复制代码


  • 不允许声明当地变量,反例如下。
build() {
  // 反例:不允许声明当地变量
  let a: number = 1;
}

  • 不允许在UI描述里直接使用console.info,但允许在方法或者函数里使用,反例如下。
build() {
  // 反例:不允许console.info
  console.info('print debug log');
}

  • 不允许创建当地的作用域,反例如下。
build() {
  // 反例:不允许当地作用域
  {
    ...
  }
}

  • 不允许调用没有效@Builder装饰的方法,允许体系组件的参数是TS方法的返回值。
  1. @Component
  2. struct ParentComponent {
  3.   doSomeCalculations() {
  4.   }
  5.   calcTextValue(): string {
  6.     return 'Hello World';
  7.   }
  8.   @Builder doSomeRender() {
  9.     Text(`Hello World`)
  10.   }
  11.   build() {
  12.     Column() {
  13.       // 反例:不能调用没有用@Builder装饰的方法
  14.       this.doSomeCalculations();
  15.       // 正例:可以调用
  16.       this.doSomeRender();
  17.       // 正例:参数可以为调用TS方法的返回值
  18.       Text(this.calcTextValue())
  19.     }
  20.   }
  21. }
复制代码



  • 不允许switch语法,如果必要使用条件判定,请使用if。反例如下。
  1. build() {
  2.   Column() {
  3.     // 反例:不允许使用switch语法
  4.     switch (expression) {
  5.       case 1:
  6.         Text('...')
  7.         break;
  8.       case 2:
  9.         Image('...')
  10.         break;
  11.       default:
  12.         Text('...')
  13.         break;
  14.     }
  15.   }
  16. }
复制代码


  • 不允许使用表达式,反例如下。
build() {
  Column() {
    // 反例:不允许使用表达式
    (this.aVar > 10) ? Text('...') : Image('...')
  }
}
通用样式

   自界说组件通过“.”链式调用的情势设置通用样式。
  
  1. @Component
  2. struct MyComponent2 {
  3.   build() {
  4.     Button(`Hello World`)
  5.   }
  6. }
  7. @Entry
  8. @Component
  9. struct MyComponent {
  10.   build() {
  11.     Row() {
  12.       MyComponent2()
  13.         .width(200)
  14.         .height(300)
  15.         .backgroundColor(Color.Red)
  16.     }
  17.   }
  18. }
复制代码
    以上自界说组件预览结果如下,可以看到ArkUI给自界说组件设置样式时,相当于给MyComponent2套了一个不可见的容器组件,而这些样式是设置在容器组件上的,而非直接设置给MyComponent2的Button组件。通过渲染结果我们可以很清楚的看到,配景颜色赤色并没有直接见效在Button上,而是见效在Button所处的开辟者不可见的容器组件上。

生命周期

  自界说组件和页面的关系:

  • 自界说组件:@Component装饰的UI单位,可以组合多个体系组件实现UI的复用。
  • 页面:即应用的UI页面。可以由一个或者多个自界说组件构成,@Entry装饰的自界说组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。
页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:

  • onPageShow:页面每次显示时触发。
  • onPageHide:页面每次隐藏时触发一次。
  • onBackPress:当用户点击返回按钮时触发。
组件生命周期,即一样平常用@Component装饰的自界说组件的生命周期,提供以下生命周期接口:

  • aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自界说组件的新实例后,在执行其build()函数之前执行。
  • aboutToDisappear:在自界说组件即将析构销毁时执行。
   生命周期流程如下图所示,下图展示的是被@Entry装饰的组件(首页)生命周期。
   

      必要注意的是,部分生命周期回调函数仅对@Entry修饰的自界说组件见效,它们分别是:onPageShow、onPageHide、onBackPress。根据上面的流程图,我们从自界说组件的初始创建、重新渲染和删除来详细解释。 
   自界说组件的创建和渲染流程


  • 自界说组件的创建:自界说组件的实例由ArkUI框架创建。
  • 初始化自界说组件的成员变量:通过当地默认值或者构造方法通报参数来初始化自界说组件的成员变量,初始化顺序为成员变量的界说顺序。
  • 如果开辟者界说了aboutToAppear,则执行aboutToAppear方法。
  • 在初次渲染的时候,执行build方法渲染体系组件,如果子组件为自界说组件,则创建自界说组件的实例。在执行build()函数的过程中,框架会观察每个状态变量的读取状态,将生存两个map:

  • 状态变量 -> UI组件(包括ForEach和if)。
  • UI组件 -> 此组件的更新函数,即一个lambda方法,作为build()函数的子集,创建对应的UI组件并执行其属性方法,表示如下。
  1. build() {
  2.   ...
  3.   this.observeComponentCreation(() => {
  4.     Button.create();
  5.   })
  6.   this.observeComponentCreation(() => {
  7.     Text.create();
  8.   })
  9.   ...
  10. }
复制代码

   当应用在配景启动时,此时应用历程并没有销毁,以是仅必要执行onPageShow。
  自界说组件重新渲染 

    当变乱句柄被触发(比如设置了点击变乱,即触发点击变乱)改变了状态变量时,或者LocalStorage / AppStorage中的属性更改,并导致绑定的状态变量更改其值时:

  • 框架观察到了变化,将启动重新渲染。
  • 根据框架持有的两个map(自界说组件的创建和渲染流程中第4步),框架可以知道该状态变量管理了哪些UI组件,以及这些UI组件对应的更新函数。执行这些UI组件的更新函数,实现最小化更新。
自界说组件的删除

   如果if组件的分支改变,或者ForEach循环渲染中数组的个数改变,组件将被删除:

  • 在删除组件之前,将调用其aboutToDisappear生命周期函数,标记取该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,当前端节点已经没有引用时,将被JS假造机垃圾回收。
  • 自界说组件和它的变量将被删除,如果其有同步的变量,比如@Link、@Prop、@StorageLink,将从同步源上取消注册。
     不建议在生命周期aboutToDisappear内使用async await,如果在生命周期的aboutToDisappear使用异步操作(Promise或者回调方法),自界说组件将被保留在Promise的闭包中,直到回调方法被执行完,这个行为阻止了自界说组件的垃圾回收。
 以下示例展示了生命周期的调用时机:
  1. import router from '@ohos.router';
  2. @Entry
  3. @Component
  4. struct MyComponent {
  5.   @State showChild: boolean = true;
  6.   // 只有被@Entry装饰的组件才可以调用页面的生命周期
  7.   onPageShow() {
  8.     console.info('Index onPageShow');
  9.   }
  10.   // 只有被@Entry装饰的组件才可以调用页面的生命周期
  11.   onPageHide() {
  12.     console.info('Index onPageHide');
  13.   }
  14.   // 只有被@Entry装饰的组件才可以调用页面的生命周期
  15.   onBackPress() {
  16.     console.info('Index onBackPress');
  17.   }
  18.   // 组件生命周期
  19.   aboutToAppear() {
  20.     console.info('MyComponent aboutToAppear');
  21.   }
  22.   // 组件生命周期
  23.   aboutToDisappear() {
  24.     console.info('MyComponent aboutToDisappear');
  25.   }
  26.   build() {
  27.     Column() {
  28.       // this.showChild为true,创建Child子组件,执行Child aboutToAppear
  29.       if (this.showChild) {
  30.         Child()
  31.       }
  32.       // this.showChild为false,删除Child子组件,执行Child aboutToDisappear
  33.       Button('create or delete Child').onClick(() => {
  34.         this.showChild = false;
  35.       })
  36.       // push到Page2页面,执行onPageHide
  37.       Button('push to next page')
  38.         .onClick(() => {
  39.           router.pushUrl({ url: 'pages/Page2' });
  40.         })
  41.     }
  42.   }
  43. }
  44. @Component
  45. struct Child {
  46.   @State title: string = 'Hello World';
  47.   // 组件生命周期
  48.   aboutToDisappear() {
  49.     console.info('[lifeCycle] Child aboutToDisappear')
  50.   }
  51.   // 组件生命周期
  52.   aboutToAppear() {
  53.     console.info('[lifeCycle] Child aboutToAppear')
  54.   }
  55.   build() {
  56.     Text(this.title).fontSize(50).onClick(() => {
  57.       this.title = 'Hello ArkUI';
  58.     })
  59.   }
  60. }
复制代码

   以上示例中,Index页面包含两个自界说组件,一个是被@Entry装饰的MyComponent,也是页面的入口组件,即页面的根节点;一个是Child,是MyComponent的子组件。只有@Entry装饰的节点才可以使页面级别的生命周期方法见效,以是MyComponent中声明了当前Index页面的页面生命周期函数。MyComponent和其子组件Child也同时也声明了组件的生命周期函数。

  • 应用冷启动的初始化流程为:MyComponent aboutToAppear --> MyComponent build --> Child aboutToAppear --> Child build --> Child build执行完毕 --> MyComponent build执行完毕 --> Index onPageShow。
  • 点击“delete Child”,if绑定的this.showChild酿成false,删除Child组件,会执行Child aboutToDisappear方法。
  • 点击“push to next page”,调用router.pushUrl接口,跳转到别的一个页面,当前Index页面隐藏,执行页面生命周期Index onPageHide。此处调用的是router.pushUrl接口,Index页面被隐藏,并没有销毁,以是只调用onPageHide。跳转到新页面后,执行初始化新页面的生命周期的流程。
  • 如果调用的是router.replaceUrl,则当前Index页面被销毁,执行的生命周期流程将变为:Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。上文已经提到,组件的销毁是从组件树上直接摘下子树,以是先调用父组件的aboutToDisappear,再调用子组件的aboutToDisappear,然后执行初始化新页面的生命周期流程。
  • 点击返回按钮,触发页面生命周期Index onBackPress,且触发返回一个页面后会导致当前Index页面被销毁。
  • 最小化应用或者应用进入配景,触发Index onPageHide。当前Index页面没有被销毁,以是并不会执行组件的aboutToDisappear。应用回到前台,执行Index onPageShow。
退出应用,执行Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

tsx81429

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表