(二)鸿蒙HarmonyOS主力开发语言ArkTS-基本语法

系列文章目录

(一)鸿蒙HarmonyOS开发基础


文章目录


前言

ArkTS是HarmonyOS主力开发语言,基于TypeScript扩展:

  1. 支持声明式UI、自定义组件,提供状态管理和渲染控制。
  2. 继承并扩展了TypeScript的所有特性,使其成为TS的超集。
  3. 未来将持续增强并行、系统类型和分布式开发等能力。

具备TypeScript基础会更容易上手ArkTS。
Typescript官方网址

一、基本语法概述

1. 示例概述

通过点击按钮,文本内容从“Hello World”变为“Hello ArkUI”,展示了ArkTS语言在UI交互中的基本应用。
图1 示例效果图

本示例中,ArkTS的基本组成如下所示。

图2 ArkTS的基本组成
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2. ArkTS基本组成

  1. 装饰器:赋予类、结构、方法以及变量特殊含义的标记。如@Entry、@Component、@State分别表示入口组件、自定义组件和状态变量。
  2. UI描述:采用声明式方式描述UI结构,如在build()方法中定义。
  3. 自定义组件:可复用的UI单元,如被@Component装饰的struct Hello。
  4. 系统组件:ArkUI框架内置的基础和容器组件,如Column、Text、Divider、Button。
  5. 属性方法:配置组件属性,如fontSize()、width()、height()等。
  6. 事件方法:设置组件事件响应逻辑,如Button的onClick()。

3. 扩展语法范式

  1. @Builder/@BuilderParam:封装和复用UI描述的特殊方法。
  2. @Extend/@Styles:扩展内置组件和封装属性样式。
  3. stateStyles:根据组件内部状态变化设置不同样式。

4. 注意事项

自定义变量不能与基础通用属性/事件名重复。

开发者可基于上述基础,结合ArkTS的声明式开发范式,进一步探索和应用ArkTS语言在UI开发中的功能。

二、声明式UI描述

创建组件

根据组件构造方法的不同,创建组件包含有参数和无参数两种方式。

说明

创建组件时不需要使用new运算符。

无参数

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

Column() {
  Text('item 1')
  Divider()
  Text('item 2')
}

有参数

如果组件的接口定义包含构造参数,则在组件后面的“()”配置相应参数。

Image组件的必选参数src

Image('https://xyz/test.jpg')

Text组件的非必选参数content

// string类型的参数
Text('test')

// $r形式引入应用资源,可应用于多语言场景
Text($r('app.string.title_value'))

// 无参数形式
Text()

变量或表达式也可以用于参数赋值,其中表达式返回的结果类型必须满足参数类型要求。

例如,设置变量或表达式来构造Image和Text组件的参数:

Image(this.imagePath)
Image('https://' + this.imageUrl)
Text(`count: ${this.count}`)

配置属性

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

配置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指向当前组件。

Button('add counter')
  .onClick(function(){
    this.counter += 2;
  }.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)

配置子组件

如果组件支持子组件配置,则需在尾随闭包"{…}"中为组件添加子组件的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')

三、创建自定义组件

在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。在进行 UI 界面开发时,通常不是简单的将系统组件进行组合使用,而是需要考虑代码可复用性、业务逻辑与UI分离,后续版本演进等因素。因此,将UI和部分业务逻辑封装成自定义组件是不可或缺的能力。

自定义组件具有以下特点:

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

自定义组件的基本用法

以下示例展示了自定义组件的基本用法。

@Component
struct HelloComponent {
  @State message: string = 'Hello, World!';

  build() {
    // HelloComponent自定义组件组合系统组件Row和Text
    Row() {
      Text(this.message)
        .onClick(() => {
          // 状态变量message的改变驱动UI刷新,UI从'Hello, World!'刷新为'Hello, ArkUI!'
          this.message = 'Hello, ArkUI!';
        })
    }
  }
}

说明

  • 如果在另外的文件中引用该自定义组件,需要使用export关键字导出,并在使用的页面import该自定义组件。
  • HelloComponent可以在其他自定义组件中的build()函数中多次创建,实现自定义组件的重用。
@Entry
@Component
struct ParentComponent {
  build() {
    Column() {
      Text('ArkUI message')
      HelloComponent({ message: 'Hello, World!' });
      Divider()
      HelloComponent({ message: '你好!' });
    }
  }
}

要完全理解上面的示例,需要了解自定义组件的以下概念定义

本文将在后面的小节中介绍:

  • 自定义组件的基本结构
  • 成员函数/变量
  • 自定义组件的参数规定
  • build()函数
  • 自定义组件通用样式
自定义组件的基本结构
  • struct:自定义组件基于struct实现,struct + 自定义组件名 + {...}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new
  • 说明:自定义组件名、类名、函数名不能和系统组件名相同。
@Component
struct MyComponent {
}
build()函数
  • build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。
@Component
struct MyComponent {
  build() {
  }
}
@Entry
  • @Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件。@Entry可以接受一个可选的LocalStorage的参数。
@Entry
@Component
struct MyComponent {
}
成员函数/变量
  • 自定义组件除了必须要实现build()函数外,还可以实现其他成员函数,成员函数具有以下约束:
    • 自定义组件的成员函数为私有的,且不建议声明成静态函数。
  • 自定义组件可以包含成员变量,成员变量具有以下约束:
    • 自定义组件的成员变量为私有的,且不建议声明成静态变量。
    • 自定义组件的成员变量本地初始化有些是可选的,有些是必选的。具体是否需要本地初始化,是否需要从父组件通过参数传递初始化子组件的成员变量,请参考状态管理。
自定义组件的参数规定
  • 在创建自定义组件的过程中,根据装饰器的规则来初始化自定义组件的参数。
@Component
struct MyComponent {
  private countDownFrom: number = 0;
  private color: Color = Color.Blue;

  build() {
  }
}

@Entry
@Component
struct ParentComponent {
  private someColor: Color = Color.Pink;

  build() {
    Column() {
      // 创建MyComponent实例,并将创建MyComponent成员变量countDownFrom初始化为10,将成员变量color初始化为this.someColor
      MyComponent({ countDownFrom: 10, color: this.someColor })
    }
  }
}
build()函数
  • 所有声明在build()函数的语言,我们统称为UI描述,UI描述需要遵循以下规则:

  • @Entry装饰的自定义组件

    • build()函数下的根节点必须是唯一的,并且必须是容器组件。
    • ForEach禁止作为根节点。
  • @Component装饰的自定义组件

    • build()函数下的根节点也是唯一且必要的,但可以是非容器组件。
    • 同样,ForEach也禁止作为根节点。

组件示例

@Entry
@Component
struct MyComponent {
  build() {
    // 根节点唯一且必要,必须为容器组件
    Row() {
      ChildComponent() 
    }
  }
}

@Component
struct ChildComponent {
  build() {
    // 根节点唯一且必要,可为非容器组件
    Image('test.jpg')
  }
}

构建函数中的限制

  • 不允许声明本地变量:在build()函数内部,不允许声明任何本地变量。
build() {
  // 反例:不允许声明本地变量
  let a: number = 1;
}
  • 禁止使用console.info:在UI描述中,直接使用console.info是不被允许的,但可以在方法或函数内部使用。
build() {
  // 反例:不允许console.info
  console.info('print debug log');
}
  • 不允许创建本地作用域:在build()函数内部,不应该使用花括号{}来创建本地作用域。
build() {
  // 反例:不允许本地作用域
  {
    ...
  }
}
  • 不允许调用没有用@Builder装饰的方法:在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语法:在build()函数中,不允许使用switch语法进行条件判断,应使用if语句代替。
build() {
  Column() {
    // 反例:不允许使用switch语法
    switch (expression) {
      case 1:
        Text('...')
        break;
      case 2:
        Image('...')
        break;
      default:
        Text('...')
        break;
    }
  }
}
  • 禁止使用表达式:在build()函数中,不应使用条件表达式。
build() {
  Column() {
    // 反例:不允许使用表达式
    (this.aVar > 10) ? Text('...') : Image('...')
  }
}

自定义组件通用样式

自定义组件可以通过链式调用的形式设置通用样式。例如:

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

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

说明:在ArkUI中,当给自定义组件设置样式时,实际上是给组件外部套了一个不可见的容器组件,样式是设置在这个容器组件上的,而非直接设置给组件内部的元素。因此,在上面的例子中,背景颜色红色实际上会应用到MyComponent2组件所在的容器上,而不是直接应用到Button组件上。

四、自定义组件与页面生命周期

在开始之前,我们先明确自定义组件和页面的关系:

  • 自定义组件:使用@Component装饰的UI单元,可以组合多个系统组件实现UI的复用,并可以调用组件的生命周期。
  • 页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。

页面生命周期

@Entry装饰的组件(页面)生命周期提供以下接口:

  • onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
  • onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
  • onBackPress:当用户点击返回按钮时触发。

组件生命周期

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

  • aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。
  • aboutToDisappear:在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。

生命周期流程

生命周期流程如下图所示,下图展示的是被@Entry装饰的组件(页面)生命周期。

根据上面的描述,我们可以从自定义组件的初始创建、重新渲染和删除来详细解释生命周期的流程。

自定义组件的创建和渲染流程

  1. 自定义组件的实例由ArkUI框架创建。
  2. 初始化自定义组件的成员变量:通过本地默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序。
  3. 如果开发者定义了aboutToAppear,则执行aboutToAppear方法。
  4. 在首次渲染的时候,执行build方法渲染系统组件,如果子组件为自定义组件,则创建自定义组件的实例。在首次渲染的过程中,框架会记录状态变量和组件的映射关系,当状态变量改变时,驱动其相关的组件刷新。
  5. 当应用在后台启动时,此时应用进程并没有销毁,所以仅需要执行onPageShow

自定义组件重新渲染

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

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

自定义组件的删除

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

  1. 在删除组件之前,将调用其aboutToDisappear生命周期函数,标记着该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,前端节点已经没有引用时,将被JS虚拟机垃圾回收。
  2. 自定义组件和它的变量将被删除,如果其有同步的变量,比如@Link@Prop@StorageLink,将从同步源上取消注册。
  3. 不建议在生命周期aboutToDisappear内使用async await,如果在生命周期的aboutToDisappear使用异步操作(Promise或者回调方法),自定义组件将被保留在Promise的闭包中,直到回调方法被执行完,这个行为阻止了自定义组件的垃圾回收。

示例

以下示例展示了生命周期的调用时机:

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

@Entry
@Component
struct MyComponent {
  @State showChild: boolean = true;

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

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

  // 组件生命周期
  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').onClick(() => {
        this.showChild = false;
      })
      // push到Page2页面,执行onPageHide
      Button('push to next page')
        .onClick(() => {
          router.pushUrl({ url: 'pages/Page2' });
        })
    }

  }
}

@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).onClick(() => {
      this.title = 'Hello ArkUI';
    })
  }
}

页面和组件生命周期说明

在示例应用中,Index 页面包含了两个自定义组件:MyComponentChildMyComponent@Entry 装饰,因此它是页面的入口组件,也是页面的根节点。ChildMyComponent 的子组件。

生命周期函数

  • 只有使用 @Entry 装饰的节点(如 MyComponent)才能使页面级别的生命周期方法生效。
  • MyComponent 和其子组件 Child 均声明了组件级别的生命周期函数。

应用冷启动初始化流程

  1. MyComponent aboutToAppear
  2. MyComponent build
  3. Child aboutToAppear
  4. Child build
  5. Child build 执行完毕
  6. MyComponent build 执行完毕
  7. Index onPageShow

删除子组件

点击“delete Child”时,由于 if 绑定的 this.showChild 变成 falseChild 组件会被删除。此时会执行 Child aboutToDisappear 方法。

跳转到新页面

  • 点击“push to next page”时,调用 router.pushUrl 接口,跳转到另一个页面。
  • 当前 Index 页面隐藏,执行 Index onPageHide
  • 由于使用的是 router.pushUrlIndex 页面并未销毁,只是隐藏。
  • 跳转到新页面后,会执行新页面的初始化生命周期流程。

替换当前页面

  • 如果调用的是 router.replaceUrl,则当前 Index 页面会被销毁。
  • 执行的生命周期流程为:Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear
  • 之后执行初始化新页面的生命周期流程。

返回按钮

  • 点击返回按钮,触发 Index onBackPress 生命周期方法。
  • 触发返回操作会导致当前 Index 页面被销毁。

应用最小化或进入后台

  • 最小化应用或应用进入后台时,触发 Index onPageHide
  • 由于 Index 页面并未被销毁,不会执行组件的 aboutToDisappear 方法。
  • 应用回到前台时,执行 Index onPageShow

退出应用

  • 退出应用时,执行 Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear 生命周期流程。

五、@Builder装饰器:自定义构建函数

前面章节介绍了如何创建一个自定义组件。该自定义组件内部UI结构固定,仅与使用方进行数据传递。ArkUI还提供了一种更轻量的UI元素复用机制@Builder@Builder所装饰的函数遵循build()函数语法规则,开发者可以将重复使用的UI元素抽象成一个方法,在build方法里调用。

为了简化语言,我们将@Builder装饰的函数也称为“自定义构建函数”。

说明

从API version 9开始,该装饰器支持在ArkTS卡片中使用。

装饰器使用说明

自定义组件内自定义构建函数

定义的语法:
@Builder MyBuilderFunction(){ ... }
使用方法:
this.MyBuilderFunction()

允许在自定义组件内定义一个或多个@Builder方法,该方法被认为是该组件的私有、特殊类型的成员函数。自定义构建函数可以在所属组件的build方法和其他自定义构建函数中调用,但不允许在组件外调用。在自定义函数体中,this指代当前所属组件,组件的状态变量可以在自定义构建函数内访问。建议通过this访问自定义组件的状态变量而不是参数传递。

全局自定义构建函数

定义的语法:
@Builder function MyGlobalBuilderFunction(){ ... }
使用方法:
MyGlobalBuilderFunction()

全局的自定义构建函数可以被整个应用获取,不允许使用thisbind方法。如果不涉及组件状态变化,建议使用全局的自定义构建方法。

参数传递规则

自定义构建函数的参数传递有按值传递和按引用传递两种,均需遵守以下规则:

  • 参数的类型必须与参数声明的类型一致,不允许undefinednull和返回undefinednull的表达式。
  • 在自定义构建函数内部,不允许改变参数值。如果需要改变参数值,且同步回调用点,建议使用@Link
  • @Builder内UI语法遵循UI语法规则。
  • 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递。

按引用传递参数

按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起@Builder方法内的UI刷新。ArkUI提供$作为按引用传递参数的范式。

overBuilder( $ : { paramA1: string, paramB1 : string } );
@Builder function overBuilder($: { paramA1: string }) {
  Row() {
    Text(`UseStateVarByReference: ${$.paramA1} `)
  }
}
@Entry
@Component
struct Parent {
  @State label: string = 'Hello';
  build() {
    Column() {
      // 在Parent组件中调用ABuilder的时候,将this.label引用传递给ABuilder
      overBuilder({ paramA1: this.label })
      Button('Click me').onClick(() => {
        // 点击“Click me”后,UI从“Hello”刷新为“ArkUI”
        this.label = 'ArkUI';
      })
    }
  }
}

按值传递参数

调用@Builder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起@Builder方法内的UI刷新。所以当使用状态变量的时候,推荐使用按引用传递。

@Builder function overBuilder(paramA1: string) {
  Row() {
    Text(`UseStateVarByValue: ${paramA1} `)
  }
}
@Entry
@Component
struct Parent {
  @State label: string = 'Hello';
  build() {
    Column() {
      overBuilder(this.label)
    }
  }
}

六、@BuilderParam装饰器:引用@Builder函数

在前面的章节中,我们学习了如何创建一个自定义组件。这个自定义组件的UI结构是固定的,主要用于与使用方进行数据传递。但是,ArkUI还为我们提供了另一种更为轻量级的UI元素复用机制,即@Builder装饰器。

使用@Builder装饰的函数遵循build()函数的语法规则。开发者可以将重复使用的UI元素抽象成一个方法,并在build方法中调用它。为了简化描述,我们将@Builder装饰的函数也称为“自定义构建函数”。

当开发者创建了自定义组件并希望为其添加特定功能时,例如在组件中添加一个点击跳转操作,直接在组件内嵌入事件方法会导致所有引入该组件的地方都增加这个功能。为解决此问题,ArkUI引入了@BuilderParam装饰器。@BuilderParam用于装饰指向@Builder方法的变量,允许开发者在初始化自定义组件时对此属性进行赋值,从而为组件增加特定的功能。这个装饰器类似于slot占位符,用于声明任意UI描述的一个元素。

说明

从API version 9开始,该装饰器支持在ArkTS卡片中使用。

装饰器使用说明

初始化@BuilderParam装饰的方法

@BuilderParam装饰的方法只能被自定义构建函数(即@Builder装饰的方法)初始化。

使用所属自定义组件的自定义构建函数或者全局的自定义构建函数,在本地初始化@BuilderParam。

@Builder function overBuilder() {}

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

  // 使用自定义组件的自定义构建函数初始化@BuilderParam
  @BuilderParam customBuilderParam: () => void = this.doNothingBuilder;
  // 使用全局自定义构建函数初始化@BuilderParam
  @BuilderParam customOverBuilderParam: () => void = overBuilder;
  build(){}
}

用父组件自定义构建函数初始化子组件@BuilderParam装饰的方法

@Component
struct Child {
  // 使用父组件@Builder装饰的方法初始化子组件@BuilderParam
  @BuilderParam customBuilderParam: () => void;

  build() {
    Column() {
      this.customBuilderParam()
    }
  }
}

@Entry
@Component
struct Parent {
  @Builder componentBuilder() {
    Text(`Parent builder `)
  }

  build() {
    Column() {
      Child({ customBuilderParam: this.componentBuilder })
    }
  }
}

注意:请确保this的指向是正确的。

在以下示例中,Parent组件在调用this.componentBuilder()时,this指向其所属组件,即“Parent”。@Builder componentBuilder()传给子组件@BuilderParam customBuilderParam,在Child组件中调用this.customBuilderParam()时,this指向在Child的label,即“Child”。

开发者需谨慎使用bind来改变函数调用的上下文,因为这可能会使this的指向变得混乱。

@Component
struct Child {
  label: string = `Child`
  @BuilderParam customBuilderParam: () => void;

  build() {
    Column() {
      this.customBuilderParam()
    }
  }
}

@Entry
@Component
struct Parent {
  label: string = `Parent`

  @Builder componentBuilder() {
    Text(`${this.label}`)
  }

  build() {
    Column() {
      this.componentBuilder()
      Child({ customBuilderParam: this.componentBuilder })
    }
  }
}

使用场景

参数初始化组件

@BuilderParam装饰的方法可以是有参数和无参数的两种形式,需与指向的@Builder方法类型匹配。

@Builder function overBuilder($$ : {label: string }) {
  Text($$.label)
    .width(400)
    .height(50)
    .backgroundColor(Color.Green)
}

@Component
struct Child {
  label: string = 'Child'
  // 无参数类型,指向的componentBuilder也是无参数类型
  @BuilderParam customBuilderParam: () => void;
  // 有参数类型,指向的GlobalBuilder1也是有参数类型的方法
  @BuilderParam customOverBuilderParam: ($$ : { label : string}) => void;

  build() {
    Column() {
      this.customBuilderParam()
      this.customOverBuilderParam({label: 'global Builder label' } )
    }

七、@Styles装饰器:定义组件重用样式

在ArkUI中,为了简化代码和提高可维护性,我们引入了@Styles装饰器,它允许开发者将重复的样式设置提炼成一个方法,并在组件声明的位置直接调用。通过这种方式,我们可以快速定义并复用自定义样式。

说明

从API version 9开始,该装饰器支持在ArkTS卡片中使用。

装饰器使用说明

  • 当前@Styles仅支持通用属性和通用事件。
  • @Styles方法不支持参数。例如,以下是一个反例:
// 反例: @Styles不支持参数
@Styles function globalFancy (value: number) {
  .width(value)
}
  • @Styles可以定义在组件内或全局。在全局定义时,需在方法名前面添加function关键字;在组件内定义时,则不需要添加function关键字。
  • @Styles定义的样式只能在当前文件内使用,不支持export

示例

全局定义
// 全局
@Styles function functionName() { 
  .width(150)
  .height(100)
  .backgroundColor(Color.Pink)
}
组件内定义
@Component
struct FancyUse {
  @State heightValue: number = 100
  
  // 定义在组件内的@Styles封装的样式
  @Styles fancy() {
    .width(200)
    .height(this.heightValue)
    .backgroundColor(Color.Yellow)
    .onClick(() => {
      this.heightValue = 200
    })
  }
  
  build() {
    Column({ space: 10 }) {
      Text('FancyA')
        .globalFancy()
        .fontSize(30)
      
      Text('FancyB')
        .fancy()
        .fontSize(30)
    }
  }
}

组件内@Styles的优先级

组件内@Styles的优先级高于全局@Styles。框架会优先查找当前组件内的@Styles,如果找不到,则会全局查找。

使用场景

以下示例中演示了组件内@Styles和全局@Styles的用法:

// 定义在全局的@Styles封装的样式
@Styles function globalFancy  () {
  .width(150)
  .height(100)
  .backgroundColor(Color.Pink)
}

@Entry
@Component
struct FancyUse {
  @State heightValue: number = 100
  
  // 定义在组件内的@Styles封装的样式
  @Styles fancy() {
    .width(200)
    .height(this.heightValue)
    .backgroundColor(Color.Yellow)
    .onClick(() => {
      this.heightValue = 200
    })
  }
  
  build() {
    Column({ space: 10 }) {
      // 使用全局的@Styles封装的样式
      Text('FancyA')
        .globalFancy()
        .fontSize(30)
      
      // 使用组件内的@Styles封装的样式
      Text('FancyB')
        .fancy()
        .fontSize(30)
    }
  }
}

在这个示例中,我们定义了一个全局的@Styles globalFancy 和一个组件内的@Styles fancy。在build方法中,我们分别在两个Text组件上使用了这两个@Styles。由于组件内的@Styles优先级更高,因此FancyB的样式将优先使用组件内的fancy样式。

八、@Extend装饰器:定义扩展组件样式

在前文的示例中,可以使用@Styles用于样式的扩展,在@Styles的基础上,我们提供了@Extend,用于扩展原生组件样式。

说明

从API version 9开始,该装饰器支持在ArkTS卡片中使用。

装饰器使用说明

语法

@Extend(UIComponentName) function functionName { ... }

使用规则

和@Styles不同,@Extend仅支持定义在全局,不支持在组件内部定义。

说明
只能在当前文件内使用,不支持export。

和@Styles不同,@Extend支持封装指定的组件的私有属性和私有事件和预定义相同组件的@Extend的方法。

// @Extend(Text)可以支持Text的私有属性fontColor
@Extend(Text) function fancy () {
  .fontColor(Color.Red)
}

// superFancyText可以调用预定义的fancy
@Extend(Text) function superFancyText(size:number) {
    .fontSize(size)
    .fancy()
}

和@Styles不同,@Extend装饰的方法支持参数,开发者可以在调用时传递参数,调用遵循TS方法传值调用。

// xxx.ets
@Extend(Text) function fancy (fontSize: number) {
  .fontColor(Color.Red)
  .fontSize(fontSize)
}

@Entry
@Component
struct FancyUse {
  build() {
    Row({ space: 10 }) {
      Text('Fancy')
        .fancy(16)
      Text('Fancy')
        .fancy(24)
    }
  }
}

@Extend装饰的方法的参数可以为function,作为Event事件的句柄。

@Extend(Text) function makeMeClick(onClick: () => void) {
  .backgroundColor(Color.Blue)
  .onClick(onClick)
}

@Entry
@Component
struct FancyUse {
  @State label: string = 'Hello World';

  onClickHandler() {
    this.label = 'Hello ArkUI';
  }

  build() {
    Row({ space: 10 }) {
      Text(`${this.label}`)
        .makeMeClick(this.onClickHandler.bind(this))
    }
  }
}

@Extend的参数可以为状态变量,当状态变量改变时,UI可以正常的被刷新渲染。

@Extend(Text) function fancy (fontSize: number) {
  .fontColor(Color.Red)
  .fontSize(fontSize)
}

@Entry
@Component
struct FancyUse {
  @State fontSizeValue: number = 20
  build() {
    Row({ space: 10 }) {
      Text('Fancy')
        .fancy(this.fontSizeValue)
        .onClick(() => {
          this.fontSizeValue = 30
        })
    }
  }
}

使用场景

以下示例声明了3个Text组件,每个Text组件均设置了fontStyle、fontWeight和backgroundColor样式。

@Entry
@Component
struct FancyUse {
  @State label: string = 'Hello World'

  build() {
    Row({ space: 10 }) {
      Text(`${this.label}`)
        .fontStyle(FontStyle.Italic)
        .fontWeight(100)
        .backgroundColor(Color.Blue)
      Text(`${this.label}`)
        .fontStyle(FontStyle.Italic)
        .fontWeight(200)
        .backgroundColor(Color.Pink)
      Text(`${this.label}`)
        .fontStyle(FontStyle.Italic)
        .fontWeight(300)
        .backgroundColor(Color.Orange)
    }.margin('20%')
  }
}

@Extend将样式组合复用,示例如下。

@Extend(Text) function fancyText(weightValue: number, color: Color) {
  .fontStyle(FontStyle.Italic)
  .fontWeight(weightValue)
  .backgroundColor(color)
}

通过@Extend组合样式后,使得代码更加简洁,增强可读性。

@Entry
@Component
struct FancyUse {
  @State label: string = 'Hello World'

  build() {
    Row({ space: 10 }) {
      Text(`${this.label}`)
        .fancyText(100, Color.Blue)
      Text(`${this.label}`)
        .fancyText(200, Color.Pink)
      Text(`${this.label}`)
        .fancyText(300, Color.Orange)
    }.margin('20%')
  }
}

九、stateStyles:多态样式

stateStyles是属性方法,可以根据UI内部状态来设置样式,类似于css伪类,但语法不同。ArkUI提供以下四种状态:

  • focused:获焦态。
  • normal:正常态。
  • pressed:按压态。
  • disabled:不可用态。

使用场景

基础场景

下面的示例展示了stateStyles最基本的使用场景。Button1处于第一个组件,Button2处于第二个组件。按压时显示为pressed态指定的黑色。使用Tab键走焦,先是Button1获焦并显示为focus态指定的粉色。当Button2获焦的时候,Button2显示为focus态指定的粉色,Button1失焦显示normal态指定的红色。

@Entry
@Component
struct StateStylesSample {
  build() {
    Column() {
      Button('Button1')
        .stateStyles({
          focused: {
            .backgroundColor(Color.Pink)
          },
          pressed: {
            .backgroundColor(Color.Black)
          },
          normal: {
            .backgroundColor(Color.Red)
          }
        })
        .margin(20)
      Button('Button2')
        .stateStyles({
          focused: {
            .backgroundColor(Color.Pink)
          },
          pressed: {
            .backgroundColor(Color.Black)
          },
          normal: {
            .backgroundColor(Color.Red)
          }
        })
    }.margin('30%')
  }
}

图1 获焦态和按压态

@Styles和stateStyles联合使用

以下示例通过@Styles指定stateStyles的不同状态。

@Entry
@Component
struct MyComponent {
  @Styles normalStyle() {
    .backgroundColor(Color.Gray)
  }

  @Styles pressedStyle() {
    .backgroundColor(Color.Red)
  }

  build() {
    Column() {
      Text('Text1')
        .fontSize(50)
        .fontColor(Color.White)
        .stateStyles({
          normal: this.normalStyle,
          pressed: this.pressedStyle,
        })
    }
  }
}

图2 正常态和按压态

在stateStyles里使用常规变量和状态变量

stateStyles可以通过this绑定组件内的常规变量和状态变量。

@Entry
@Component
struct CompWithInlineStateStyles {
  @State focusedColor: Color = Color.Red;
  normalColor: Color = Color.Green

  build() {
    Column() {
      Button('clickMe').height(100).width(100)
        .stateStyles({
          normal: {
            .backgroundColor(this.normalColor)
          },
          focused: {
            .backgroundColor(this.focusedColor)
          }
        })
        .onClick(() => {
          this.focusedColor = Color.Pink
        })
        .margin('30%')
    }
  }
}

Button默认normal态显示绿色,第一次按下Tab键让Button获焦显示为focus态的红色,点击事件触发后,再次按下Tab键让Button获焦,focus态变为粉色。

图3 点击改变获焦态样式

总结

  1. 基本语法概述:这是关于编程语言或框架的基本语法的一个简要介绍。
  2. 声明式UI描述:指的是通过代码直接描述UI的结构和样式,而不是通过命令式编程来逐步构建UI。
  3. 自定义组件:允许开发者创建自己的UI组件,这些组件可以具有特定的功能、样式和行为。
  4. @Builder装饰器:这是一个用于自定义构建函数的装饰器。它可以帮助开发者更方便地创建和配置组件。
  5. @BuilderParam装饰器:这是一个与@Builder装饰器配合使用的装饰器,用于引用@Builder函数中的参数。
  6. @Styles装饰器:这个装饰器允许开发者定义组件的重用样式,使得这些样式可以在多个组件之间共享。
  7. @Extend装饰器:这个装饰器用于定义扩展组件的样式,允许开发者在基础样式的基础上进行修改或扩展。
  8. stateStyles:多态样式:指的是根据组件的不同状态(如:悬停、按下、禁用等)来定义不同的样式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值