基础概念

基础概念
主题 简介

ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在[TypeScript](https://www.typescriptlang.org/)(简称TS)生态基础上做了进一步扩展,保持了TS的基本风格,同时通过规范定义强化开发期静态检查和分析,提升程序执行稳定性和性能。

ArkTS的一大特性是它专注于低运行时开销。ArkTS对TypeScript的动态类型特性施加了更严格的限制,以减少运行时开销,提高执行效率。通过取消动态类型特性,ArkTS代码能更有效地被运行前编译和优化,从而实现更快的应用启动和更低的功耗。

注意:从API version 10开始,ArkTS进一步通过规范强化静态检查和分析对比标准TS的差异可以参考:

当前,在UI开发框架中,ArkTS主要扩展了如下能力:

基本语法

ArkTS定义了声明式UI描述、自定义组件和动态扩展UI元素的能力,再配合ArkUI开发框架中的系统组件及其相关的事件方法、属性方法等共同构成了UI开发的主体。

#d

状态管理

ArkTS提供了多维度的状态管理机制。在UI开发框架中,与UI相关联的数据可以在组件内使用,也可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,还可以在应用全局范围内传递或跨设备传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活地利用这些能力来实现数据和UI的联动。

渲染控制

ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。数据懒加载从数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。

从TypeScript到ArkTS的适配规则

主题 适配规则

没有next文档看这个:[OpenHarmony文档适配规则](https://docs.openharmony.cn/pages/v4.1/zh-cn/application-dev/quick-start/typescript-to-arkts-migration-guide.md)

有next文档看这个:[HarmonyOS适配规则](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/typescript-to-arkts-migration-guide-0000001820879565)

UI范式

范式?意思就是我们要统一遵守的UI写法

主题 基本组成

主题 说明

自定义组件部分和UI描述,系统组件,事件方法,属性方法我们也一般把他们成为ArkUI自定义变量不能与基础通用属性/事件名重复。

装饰器

用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如上述示例中@Entry、@Component和@State都是装饰器,

  • @Component:表示自定义组件,

  • @Entry:表示该自定义组件为入口组件,

  • @State:表示组件中的状态变量,状态变量变化会触发UI刷新。

UI描述

以声明式的方式来描述UI的结构,例如build()方法中的代码块。

自定义组件

可复用的UI单元,可组合其他组件,如上述被@Component装饰的struct Hello。

系统组件

ArkUI框架中默认内置的基础和容器组件,可直接被开发者调用,比如示例中的Column、Text、Divider、Button。

属性方法

组件可以通过链式调用配置多项属性,如fontSize()、width()、height()、backgroundColor()等。

事件方法

组件可以通过链式调用设置多个事件的响应逻辑,如跟随在Button后面的onClick()。

当然除了以上内容还有其他的一些装饰器和组件,我们后面会逐渐讲到

声明式描述UI

创建组件

我们创建组件,不需要和TypeScript一样new一个对象,而是直接使用组件名称

  • 创建组件一般我们有两种情况,一种是**有参**,一种是**无参**

1. 无参创建

//如果组件的接口定义没有包含必选构造参数,则组件后面的“()”不需要配置任何内容。
Column() {
}

**如何快速甄别是否可以无参创建组件?**

  • **我们可以按住键盘ctrl,此时会出现一个可点击的效果,我们点击进去看看**

![](https://cdn.nlark.com/yuque/0/2024/png/327272/1717072945737-e7f572e0-973b-4cc3-97ef-bb45a6d7c1d2.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_28%2Ctext_56iL5bqP5ZGY5L2p5aWH5LiT5bGe77yM55uX54mI5b-F56m2%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10)

  • 此时这里Column有一个类型,我们继续ctrl点击进入

![](https://cdn.nlark.com/yuque/0/2024/png/327272/1717073010380-524b9095-2c59-4b49-8ff9-cc8da0c1bbc3.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_23%2Ctext_56iL5bqP5ZGY5L2p5aWH5LiT5bGe77yM55uX54mI5b-F56m2%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10)

  • 这里我们就可以看到具体类型定义的interface结构了,我们可以看到这里面都是通过**?问号**也就是可选参数,没有必填的参数,也就意味着定义的时候,可以不传构造参数进去。

  • 同理如果有没使用?问号的,也就是必传参数,也就出现咱们下面的有参创建组件

![](https://cdn.nlark.com/yuque/0/2024/png/327272/1717073029100-3b8376a8-0920-4de9-86ba-d7390750bb25.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_20%2Ctext_56iL5bqP5ZGY5L2p5aWH5LiT5bGe77yM55uX54mI5b-F56m2%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10)

总结:如果源码中,接口定义的函数中,传入的参数都为可选参数,或者没有参数,那么定义创建组件使用的时候,就可以用无参创建

2. 有参创建

Image('https://xyz/test.jpg')
  • 同样我们进去看一下,为什么必须传参数才能创建?

![](https://cdn.nlark.com/yuque/0/2024/png/327272/1717073239023-90116f8d-6df5-44e4-9a5c-3b91b8292635.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_48%2Ctext_56iL5bqP5ZGY5L2p5aWH5LiT5bGe77yM55uX54mI5b-F56m2%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10)

![](https://cdn.nlark.com/yuque/0/2024/png/327272/1717073267587-f1760383-f877-4610-bdda-9d2358f7f1e5.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_42%2Ctext_56iL5bqP5ZGY5L2p5aWH5LiT5bGe77yM55uX54mI5b-F56m2%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10)

  • 在这里我们就可以看出,我们用ctrl鼠标移动到组件上,他就会提示上面的定义方式,我们可以快速看出,它的构造必须传一个src的参数,这也就是我们为什么使用image组件必须有参创建的原因

3. 函数类型接口

//函数类型接口,里面只有函数签名,不包含属性
interface ImageInterface {
  //调用签名
  (src: image.PixelMap | ResourceStr | DrawableDescriptor): ImageAttribute;
}

上面看到有参无参创建方式,我们这里就要提一下**函数类型接口****调用签名**

**什么是调用签名?**

**调用签名是描述函数类型的一种语法,用于指定函数的参数类型和返回类型**

**当然一个接口里面可以包含一个或者多个调用签名**

**也就是上面interface里面的函数描述 (src: image.PixelMap | ResourceStr | DrawableDescriptor): ImageAttribute;**

**反之函数类型接口就是我们的接口定义啦,只不过里面包含的内容是函数,一个或者多个**

4. 为什么不写参数名去创建组件?

  • 因为这里涉及到JavaScript的基础知识,形参和实参

//这里面就是相当于调用接口的函数,给函数传值叫做实参
Image('https://xyz/test.jpg')
interface ImageInterface {
  //这里的参数就是形参,
  (src: image.PixelMap | ResourceStr | DrawableDescriptor): ImageAttribute;
}
  • 简单直观来说就是这样

function createUser(name, age, email) {
  console.log(`Name: ${name}, Age: ${age}, Email: ${email}`);
}

createUser("Alice", 30, "alice@example.com");

5. 什么时候指定参数名?

  • 当我们函数定义需要**使用对象作为参数**的时候,需要指定参数名

function createUser({ name, age, email }) {
  console.log(`Name: ${name}, Age: ${age}, Email: ${email}`);
}

createUser({ name: "Alice", age: 30, email: "alice@example.com" });
  • 同理如果我们给Column组件传入一个非必须的参数的时候,因为它定义的参数是一个对象

Column({space:10}){
}

![](https://cdn.nlark.com/yuque/0/2024/png/327272/1717074367694-d9cd6ae5-6297-4875-8837-a8a70ebede54.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_22%2Ctext_56iL5bqP5ZGY5L2p5aWH5LiT5bGe77yM55uX54mI5b-F56m2%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10)

属性配置

属性方法以“.”链式调用的方式配置系统组件的样式和其他属性,建议每个属性方法单独写一行

  • **配置Text组件的字体大小。**

Text('test')
  .fontSize(12)
  • **配置组件的多个属性。**

Image('test.jpg')
  .alt('error.jpg')    
  .width(100)    
  .height(100)
  • **传递变量或表达式**

Text('hello')
  .fontSize(this.size)
Image('test.jpg')
  .width(this.count % 2 === 0 ? 100 : 200)    
  .height(this.offset + 100)
  • **枚举类型可以作为参数传递:**

  • 对于系统组件,ArkUI还为其属性预定义了一些**枚举类型**供开发者调用,但必须满足参数类型要求。

例如,可以按以下方式配置Text组件的颜色和字体样式。

Text('hello')
  .fontSize(20)
  .fontColor(Color.Red)
  .fontWeight(FontWeight.Bold)
事件

事件方法以“.”链式调用的方式配置系统组件支持的事件,建议每个事件方法单独写一行。

  • **使用箭头函数配置组件的事件方法。**

当然里面传进去的函数是一个匿名函数表达式,要求使用“**() => {...}”**

Button('Click me')
  .onClick(() => {
    this.myText = 'ArkUI';
  })

**如果不想使用匿名函数怎么办?**

  • **使用普通的成员函数:**需要bind this。

myClickHandler(): void {
  this.counter += 2;
}
...
Button('add counter')
  .onClick(this.myClickHandler.bind(this))
  • **单独声明的箭头函数:**可以直接调用,不需要bind this。

fn = () => {
  console.info(`counter: ${this.counter}`)
  this.counter++
}
  ...
  Button('add counter')
  .onClick(this.fn)

关于箭头函数this

在ArkTS中,由于箭头函数this指向问题,**箭头函数中不允许使用this**,箭头函数内部的this是词法作用域,由上下文确定。匿名函数可能会有this指向不明确问题,在ArkTS中不允许使用。

![](https://cdn.nlark.com/yuque/0/2024/png/327272/1717075058710-01c591b2-8f5c-4c27-a749-a00363604d98.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_72%2Ctext_56iL5bqP5ZGY5L2p5aWH5LiT5bGe77yM55uX54mI5b-F56m2%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10)

配置子组件

如果组件支持子组件配置,则需在**尾随闭包"{...}"**中为组件添加子组件的UI描述。Column、Row、Stack、Grid、List等组件都是容器组件。

  • 以下是简单的Column组件配置子组件的示例。

Column() {
  Text('Hello')
    .fontSize(100)
  Divider()
  Text(this.myText)
    .fontSize(100)
    .fontColor(Color.Red)
}
  • 容器组件均支持子组件配置,可以实现相对复杂的多级嵌套。

Column() {
  Row() {
    Image('test1.jpg')
      .width(100)
      .height(100)
    Button('click +1')
      .onClick(() => {
        console.info('+1 clicked!');
      })
  }
}
尾随闭包

在编程中,尾随闭包是一种将函数的最后一个参数(如果是闭包)直接写在函数调用的圆括号外面的语法糖,使代码更简洁清晰。

如何查看组件是否包含子组件?

最后我们可以通过组件文档,查看组件是否包含子组件

我们鼠标移动到组件上,就可以提示出“查阅API参考”的字样

![](https://cdn.nlark.com/yuque/0/2024/png/327272/1717076780705-a627b1cc-4d90-4aa0-8e65-9777273c8a7b.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_78%2Ctext_56iL5bqP5ZGY5L2p5aWH5LiT5bGe77yM55uX54mI5b-F56m2%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10)

自定义组件

\

概述

在ArkUI中,**UI显示的内容均为组件**

**系统组件:**由框架直接提供的称为系统组件

**自定义组件:**由开发者定义的称为自定义组件。

特点

  • **可组合:**允许开发者组合使用系统组件、及其属性和方法。

  • **可重用:**自定义组件可以被其他组件重用,并作为不同的实例在不同的父组件或容器中使用。

  • **数据驱动UI更新:**通过状态变量的改变,来驱动UI的刷新。

组件的基本结构

  1. @Component

  2. struct 组件名{

  3. build(){}

  4. }

@Component

@Component装饰器仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的能力,一个struct只能被一个@Component装饰。@Component可以接受一个可选的bool类型参数。

struct

自定义组件基于struct实现,struct + 自定义组件名 + {...}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。

@Component
struct HelloPage {
  build() {
    Column(){
      Button(){}
    }
  }
}

注意:自定义组件名、类名、函数名不能和系统组件名相同。

build()

build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。并且struct中只能有一个build函数

@Component
struct MyComponent {
  build() {
  }
}
@Entry

@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件。

错误示例:一个UI页面中,只能有一个@Entry

![](https://cdn.nlark.com/yuque/0/2024/png/327272/1717078173163-195125a9-94fe-45be-8295-b0be2db73acc.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_48%2Ctext_56iL5bqP5ZGY5L2p5aWH5LiT5bGe77yM55uX54mI5b-F56m2%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10)

/

未命名routeName
  • 命名路由跳转选项。

  • 此配置项主要为了共享包har或者hsp的页面跳转,后面router会详细讲解

@Entry({ routeName : 'myPage' })
@Component
struct MyComponent {
}
@Reusable
  • @Reusable装饰的自定义组件具备可复用能力,主要用于大数据量列表加载时,主要提高列表页面的加载速度和响应速度,后面会详细讲解

/

@Reusable
@Component
struct MyComponent {
}
成员函数/变量

自定义组件可以包含成员变量,成员变量具有以下约束:

  • 自定义组件的成员变量为私有的,且不建议声明成静态变量。

  • 自定义组件的成员变量本地初始化有些是可选的,有些是必选的。具体是否需要本地初始化,是否需要从父组件通过参数传递初始化子组件的成员变量

@Component
struct MyComponent {
  private countDownFrom: number = 0;
  private color: Color = Color.Blue;

  build() {
  }
}
build()函数
  • build()函数中只能有一个根节点

@Component
struct ChildComponent {
  build() {
    // 根节点唯一且必要
    Image('test.jpg')
  }
}
  • **@Entry修饰的组件和@Component修饰的组件中,对于根节点的要求不一样**

    • @Entry中,**根节点必须是容器组件**( 主要用于**布局和组织子组件** ,就是布局组件和有子组件的组件例如Button,Text等)

@Entry
@Component
struct MyComponent {
  build() {
    // 根节点唯一且必要,必须为容器组件
    Row() {
      ChildComponent() 
    }
  }
}
    • @Component中,**根节点可以是非容器组件,**也就是可以是任何的组件,比如Image组件(既不是布局组件,也不能有子组件)

@Component
struct ChildComponent {
  build() {
    // 根节点唯一且必要,可为非容器组件
    Image('test.jpg')
  }
}
  • 不允许声明本地变量,反例如下。

build() {
  // 反例:不允许声明本地变量
  let a: number = 1;
}
  • 不允许在UI描述里直接使用console.info,但允许在方法或者函数里使用,反例如下。

build() {
  // 反例:不允许console.info
  console.info('print debug log');
}
  • 不允许创建本地的作用域,反例如下。

build() {
  // 反例:不允许本地作用域
  {
    ...
  }
}
  • 不允许调用没有用@Builder装饰的方法,也就是普通函数不能调用(作为参数的返回值例外),允许系统组件的参数是TS方法的返回值。

@Component
  struct ParentComponent {
    doSomeCalculations() {
    }


    calcTextValue(): string {
      return 'Hello World';
    }


    @Builder doSomeRender() {
      Text(`Hello World`)
    }


    build() {
      Column() {
        // 反例:不能调用没有用@Builder装饰的方法
        this.doSomeCalculations();
        // 正例:可以调用
        this.doSomeRender();
        // 正例:参数可以为调用TS方法的返回值
        Text(this.calcTextValue())
      }
    }
  }
  • 不允许使用switch语法,如果需要使用条件判断,请使用if。反例如下。

build() {
  Column() {
    // 反例:不允许使用switch语法
    switch (expression) {
      case 1:
        Text('...')
        break;
      case 2:
        Image('...')
        break;
      default:
        Text('...')
        break;
    }
  }
}
  • 不允许使用表达式,反例如下。

build() {
  Column() {
    // 反例:不允许使用表达式
    (this.aVar > 10) ? Text('...') : Image('...')
  }
}
  • 不允许直接改变状态变量,反例如下。

@Component
  struct CompA {
    @State col1: Color = Color.Yellow;
    @State col2: Color = Color.Green;
    @State count: number = 1;
    build() {
      Column() {
        // 应避免直接在Text组件内改变count的值
        Text(`${this.count++}`)
          .width(50)
          .height(50)
          .fontColor(this.col1)
          .onClick(() => {
            this.col2 = Color.Red;
          })
        Button("change col1").onClick(() =>{
          this.col1 = Color.Pink;
        })
      }
      .backgroundColor(this.col2)
    }
  }

为什么不能直接改变变量?

全量更新: ArkUI可能会陷入一个无限的重渲染的循环里,因为Text组件的每一次渲染都会改变应用的状态,就会再引起下一轮渲染的开启。 当 this.col2 更改时,都会执行整个build构建函数,因此,Text(\${this.count++})绑定的文本也会更改,每次重新渲染Text(\${this.count++}),又会使this.count状态变量更新,导致新一轮的build执行,从而陷入无限循环。

自定义组件通用样式

自定义组件通过“.”链式调用的形式设置通用样式。

注意:自定义组件定义后,尤其是父组件引用子组件的时候,只能使用通用的样式

@Component
struct MyComponent2 {
  build() {
    Button(`Hello World`)
  }
}

@Entry
@Component
struct MyComponent {
  build() {
    Row() {
      MyComponent2()
        .width(200)
        .height(300)
        .backgroundColor(Color.Red)
    }
  }
}

ArkUI给自定义组件设置样式时,相当于给MyComponent2**套了一个不可见的容器组件**,而这些样式是设置在容器组件上的,而**非直接设置给MyComponent2的Button组件**。通过渲染结果我们可以很清楚的看到,背景颜色红色并没有直接生效在Button上,而是生效在Button所处的开发者不可见的容器组件上。

自定组件之父子组件

父子组件

其实,不管是父亲还是孩子,他们都是组件,只不过是一个组件中包含了一个或者多个其他组件,被包含进去的组件也叫做子组件,包含子组件的也就成为了父组件

在单UI页面中
@Entry
@Component
struct Father {
  build() {
    Column(){
      Button(`Hello World`)
        .padding({ top: '0.00vp', right: '16.00vp', bottom: '0.00vp', left: '30.00vp' })
      Child()
    }

  }
}

@Component
struct Child {
  build() {
    Row() {
      Text("子组件")
    }
  }
}
多页面中

子组件需要通过export或者export default导出,父组件需要import导入子组件才可使用

Father.ets

import { Child } from './Child'

@Entry
@Component
struct Father {
  build() {
    Column(){
      Button(`Hello World`)
        .padding({ top: '0.00vp', right: '16.00vp', bottom: '0.00vp', left: '30.00vp' })
      Child()
    }

  }
}

Child.ets

@Component
export struct Child {
  build() {
    Row() {
      Text("子组件")
    }
  }
}

页面和自定义组件的生命周期

页面生命周期

即被@Entry装饰的组件生命周期,提供以下生命周期接口:

onPageShow

页面每次显示时触发一次,包括路由过程、应用进入前台等场景。

  • 一般用于数据的重新刷新

  • 初始状态的恢复

  • 动画启动

  • 统计和分析数据

  • 事件的一些监听

onPageHide

页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。

  • 主要用来保存数据,释放资源等

  • onBackPress:当用户点击返回按钮时触发。

组件生命周期

即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:

aboutToAppear

组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。

  • 一般用于数据加载,初始化数据

  • 设置页面的状态

  • 预加载资源,比如加载一些图片,视频等

  • 注册时间监听,比如键盘,网络状态变化等

  • 更新数据内容,或者动态的一些数据比如计数器,时间等

onDidBuild

组件build()函数执行完成之后回调该接口,不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现。

  • 绑定事件、设置初始状态、调用其他方法

aboutToDisappear

aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。

  • 主要用于清理一些资源

示例:

// Index.ets
import router from '@ohos.router';

@Entry
@Component
struct MyComponent {
  @State showChild: boolean = true;
  @State btnColor:string = "#FF007DFF"

  // 只有被@Entry装饰的组件才可以调用页面的生命周期
  onPageShow() {
    console.info('Index onPageShow');
  }
  // 只有被@Entry装饰的组件才可以调用页面的生命周期
  onPageHide() {
    console.info('Index onPageHide');
  }

  // 只有被@Entry装饰的组件才可以调用页面的生命周期
  onBackPress() {
    console.info('Index onBackPress');
    this.btnColor ="#FFEE0606"
    return true // 返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理
  }

  // 组件生命周期
  aboutToAppear() {
    console.info('MyComponent aboutToAppear');
  }

  // 组件生命周期
  aboutToDisappear() {
    console.info('MyComponent aboutToDisappear');
  }

  build() {
    Column() {
      // this.showChild为true,创建Child子组件,执行Child aboutToAppear
      if (this.showChild) {
        Child()
      }
      // this.showChild为false,删除Child子组件,执行Child aboutToDisappear
      Button('delete Child')
      .margin(20)
      .backgroundColor(this.btnColor)
      .onClick(() => {
        this.showChild = false;
      })
      // push到page页面,执行onPageHide
      Button('push to next page')
        .onClick(() => {
          router.pushUrl({ url: 'pages/page' });
        })
    }

  }
}

@Component
struct Child {
  @State title: string = 'Hello World';
  // 组件生命周期
  aboutToDisappear() {
    console.info('[lifeCycle] Child aboutToDisappear')
  }
  // 组件生命周期
  aboutToAppear() {
    console.info('[lifeCycle] Child aboutToAppear')
  }

  build() {
    Text(this.title).fontSize(50).margin(20).onClick(() => {
      this.title = 'Hello ArkUI';
    })
  }
}

自定义组件的自定义布局(后面会单独讲解)

如果需要通过测算的方式布局自定义组件内子组件的位置,建议使用以下接口

  • **onMeasureSize:**组件每次布局时触发,计算子组件的尺寸,其执行时间先于onPlaceChildren。

  • **onPlaceChildren:**组件每次布局时触发,设置子组件的起始位置。

// xxx.ets
@Entry
@Component
struct Index {
  build() {
    Column() {
      CustomLayout({ builder: ColumnChildren })
    }
  }
}

// 通过builder的方式传递多个组件,作为自定义组件的一级子组件(即不包含容器组件,如Column)
@Builder
function ColumnChildren() {
  ForEach([1, 2, 3], (index: number) => { // 暂不支持lazyForEach的写法
    Text('S' + index)
      .fontSize(30)
      .width(100)
      .height(100)
      .borderWidth(2)
      .offset({ x: 10, y: 20 })
  })
}

@Component
struct CustomLayout {
  @Builder
  doNothingBuilder() {
  };

  @BuilderParam builder: () => void = this.doNothingBuilder;
  @State startSize: number = 100;
  result: SizeResult = {
    width: 0,
    height: 0
  };

  // 第一步:计算各子组件的大小
  onMeasureSize(selfLayoutInfo: GeometryInfo, children: Array<Measurable>, constraint: ConstraintSizeOptions) {
    let size = 100;
    children.forEach((child) => {
      let result: MeasureResult = child.measure({ minHeight: size, minWidth: size, maxWidth: size, maxHeight: size })
      size += result.width / 2;
    })
    this.result.width = 100;
    this.result.height = 400;
    return this.result;
  }
  // 第二步:放置各子组件的位置
  onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Array<Layoutable>, constraint: ConstraintSizeOptions) {
    let startPos = 300;
    children.forEach((child) => {
      let pos = startPos - child.measureResult.height;
      child.layout({ x: pos, y: pos })
    })
  }

  build() {
    this.builder()
  }
}

自定义组件冻结功能

后续讲解

自定义组件处于非激活状态时,状态变量将不响应更新,即@Watch不会调用,状态变量关联的节点不会刷新。通过freezeWhenInactive属性来决定是否使用冻结功能,不传参数时默认不使用。支持的场景有:页面路由,TabContent,LazyforEach,Navigation。

自定义组件监听页面生命周期

(路由中讲解)

基础概念
姜帝屋的头像
创建于:04-05
随记
讨论
媒体