当前位置:   article > 正文

带你了解ArkTs的基本语法

arkts

本文是结合HarmonyOS3.1/4.0的开发文档,结合一些代码示例而成的,如有不妥,请告知。

前言

ArkTs是HarmonyOS主推的应用开发语言。ArkTs是基于Typescript进一步扩展出来的产物,继承了Typescript的所有特性,是typescript的超集。

不过,ArkTs还扩展了如下的能力:

  • 基本语法:ArkTs定义了声明式UI描述、自定义组件和动态扩展UI元素的能力;
  • 状态管理:ArkTs提供了多维度的状态管理机制。在UI开发框架中,与UI相关联的数据可以在组件内使用,也可以在不同组件层级间传递,解决了相同以及不同层级组件之间的通讯。
  • 渲染控制:ArkTs提供了渲染控制能力条件渲染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件,数据懒加载从数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。

1 基本语法

因为ArkTs是基于Typescript扩展的,所以语法基本跟Typescript一致的,需要先熟悉一下Typescript,再来学习ArkTs,如果没有了解过Typescript的同学,可以去Typescript的官网看看。

1.1 基本语法概述

了解ArkTs的基本语法,我们将从下面一段代码去分析一下

@Entry // 装饰器
@Component // 装饰器
// struct Index:定义自定义组件
struct Index {
  // @State 装饰器
  @State message: string = 'Hello World'
  
  // 整个build 方法里的内容属于UI描述,Row、Column等属于内置组件
  build() {
    Row() {
      Column() {
        Text(this.message)
          // 事件方法
          .onClick(() => {
            console.log('---');
          })
          // 下面两个是属性方法
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')

      // DemoComponent()
    }
    .height('100%')
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

具体描述:

  • 装饰器:用于装饰类、结构、方法以及变量,并赋予特殊含义。如上述的代码片段中@Entry/@Component/@State都属于装饰器,@表示自定义组件,@Entry表示该自定义组件为入口组件,@State表示组件中的状态变量,类似于赋予变量响应式,状态变量值发生变化会触发组件更新。
  • UI描述:以声明式的方式来描述UI的结构,如build方法中的代码块(有点类似Flutter)。
  • 自定义组件:可复用的UI单元,可组合其它组件。如上面代码被@Component装饰的struct Index。struct是固定范式,类似于定义类的class。
  • 内置组件:ArkUI框架中默认内置的基础组价和容器组件,可直接使用,无需引入。
    • 容器组件:Row、Column等。
    • 基础组件:Text、Button等
  • 属性方法:在ArkTs中组件是可以通过链式调用配置多项属性的,如上述的fontSize()、fontWeight()、height()等。
  • 事件方法:组件可以通过链式调用设置多个事件的响应逻辑,如上述代码中Text后面的onClick()。

其它更多详细的系统组件、属性方法等介绍,可以参考 ArkTs的声明式开发范式

1.2 声明式UI简介

Harmony主推的ArkTs,为了帮助开发者实现应用交互逻辑,以声明方式组合和扩展组件来描述应用程序的UI,同时提供了可链式调用的基本属性、事件方法等,极大程度地帮助开发者提高开发效率。

1.2.1 创建组件

根据ArkTs提供的组件构造方法不同,有两种方式创建组件:有参和无参

注意:因为struct 关键字的原因,创建组件的时候不需要通过new运算符去创建

1.2.1.1 无参数组件

当组件的接口定义了无参构造函数,则组件后面的"()"不需要传递任何参数,内置组件Divider就是如此。

Row() {
    Button("确定").onClick(() => {
        this.btnText = "删除";
        console.log("======", this.btnText);
    }).width(120)
        .height(40)
        .backgroundColor(Color.White)
        .fontColor("#ff2064")

    // 分割线
    Divider()
    // 文本显示
    Text(this.btnText)
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
1.2.1.2 有参数组件

当组件的接口定义了有参构造函数(参数分必填和可选),则在组件后面的"()"传递对应的参数。

  • Button组件的非必填参数options

button组件源码中构造参数定义,如下图:

在这里插入图片描述

// 无参形式
Button()
  .width(120)
  .height(40)
// 有参形式:string类型参数以及非必填参数options
Button("确定", { type: ButtonType.Capsule })
  .width(120)
  .height(40)
  .backgroundColor(Color.Orange)
  .fontColor("#fff")
// $r形式引入应用资源,可用于多语言环境 title_value为自定义属性,其配置如下图
Button($r('app.string.title_value'))
  .width(120)
  .height(40)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在这里插入图片描述

在这里插入图片描述

  • Image组件的必填参数src
// 网络图片在真机或虚拟机上需要配置网络请求权限才可以
Image("https://test/test.png") 
  • 1
  • 2
1.2.2 配置属性

组件属性方法以"."链式调用方式进行配置内置组件的样式和其他属性

  • 配置Button组件的字体颜色
Button("按钮")
  .fontColor("#fff")
  • 1
  • 2
  • 配置组件的多个属性
Button("确定", { type: ButtonType.Capsule })
    .width(120)
    .height(this.height ? this.height : 20)
    .fontColor("#fff")
  • 1
  • 2
  • 3
  • 4
  • 对于内置组件,ArkUI还为其属性定义了一些枚举类型给开发者调用,例如Button组件的type等
Button("确定", { type: ButtonType.Capsule })

Text("Hello ArkTs")
    .fontColor(Color.Orange)
    .fontWeight(FontWeight.Bold)
  • 1
  • 2
  • 3
  • 4
  • 5
1.2.3 配置事件

事件方法跟其他属性一样,也是以"."链式调用的方式使用,下面介绍一下几种事件方法。

  • 使用箭头函数配置组件的事件方法
Button($r('app.string.title_value'))
    .onClick(() => {
      console.log("这是一个箭头函数");
    })
  • 1
  • 2
  • 3
  • 4
  • 使用匿名函数表达式配置组件的事件方法,这里需要保证函数体的this指向当前组件。
Button($r('app.string.title_value'))
    .onClick(function(){
      this.btnText = "测试";
      console.log("===this====", this.btnText); // 输出:测试
    }.bind(this))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 使用成员函数来配置组件的事件方法
// 定义成员函数跟定义变量同级
handleClick(): number {
    return 100;
}

...

Button($r('app.string.title_value'))
    .onClick(this.handleClick.bind(this))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 使用声明的箭头函数配置组件的事件方法,不行bind this
struct DemoComponent {
  @State btnText: string = 'Hello World'
  
  handleFn = (): number => {
    return 100;
  }

  build() {
    Row() {
      Button($r('app.string.title_value'))
        .onClick(this.handleFn)
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
1.2.4 配置子组件

组件如果支持子组件配置,则需在闭包"{…}"中为组件添加子组件。

  • 下面是简单的Row组件配置子组件的示例
Row() {
    Text("这是一行文本!")
    Divider()
    Button("删除按钮")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 在内置组件中容器组件都支持子组件配置,可以实现一些复杂的嵌套
Row() {
  Column() {
    Row() {
      Text("这是一行文本!")
        .fontSize(14)
        .fontColor(Color.Red)
    }
    Row() {
      Button("删除")
        .onClick(() => {
          console.log("删除操作");
        })
    }
    .justifyContent(FlexAlign.End)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
1.3 自定义组件

要实现一个复杂的功能,基本都不会在同一个组件中实现整个业务逻辑,这个时候需要对整个功能页面进行拆解成不同的小块,这时候自定义组件就派上用场了。

1.3.1 创建自定义组件

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

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

说明:如果该组件需要给到其它组件调用,则需要使用关键字export导出,使用需要使用关键字import导入

@Component
export struct Child {
  @State count: number = 0;
  
  build() {
    Row() {
      Text(`the count is ${this.count}`)
      Button("点击+1")
        .onClick(() => {
          this.count += 1;
        })
    }
    .width("100%")
    .height("100%")
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在其它组件中使用child

import { Child } from './child'

@Component
struct Father {

  build() {
    Column() {
      Text("这是父组件!")
      // 在子组件定义的count变量会编程可选参数
      Child({ count: 20 })
      Divider()
      // 多次重复使用
      Child()
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
1.3.2 自定义组件简介

上面简单讲了自定义组件的基本使用,这里我们将对自定义组件做进一步的了解。

  • 自定义组件的基本结构

    • struct:自定义组件是基于struct实现的,struct + 组件名称 + {…}的组合构成了自定义组件,而且不允许有继承关系。struct实例化的时候,可以省略new关键字。

    注意:自定义组件组件名、类名、函数名都能和内置组件名相同。

    • @Component:该装饰器仅能装饰struct关键字声明的数据结构,自定义组件必须有该装饰器声明。struct被@Component装饰后,具备组件化能力,而实现build方法是用来描述UI,同时一个struct只能被一个@Component装饰。

    说明:从API 9 开始,@Component装饰器支持在ArkTs卡片中使用。

    • build()函数:该函数用于自定义组件的声明式UI描述,自定义组件必须实现该方法,否则报错。
    @Component
    struct Demo1 {
        build() {
            ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • @Entry:该装饰器装饰的自定义组件将作为页面的入口。在单个UI页面中,有且只有一个@Entry装饰一个自定义组件。该装饰器接收一个可选localStorage参数。

    说明:该装饰器也是从API 9 开始支持在ArkTs 卡片中使用

  • 成员函数和变量

自定义组件除了必须实现的build函数,还有其他一些可选成员函数,比如onPageShow、aboutToDisappear等,具体可自查其源码。成员函数具有以下约束:

  1. 不支持静态函数
  2. 成员函数的访问是私有的

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

  1. 不支持静态成员变量
  2. 所有成员变量都是私有的,变量的访问规则与成员函数访问规则相同。
  3. 自定义组件的成员变量初始化有些是可选的,有些是必选的,具体看是否需要本地初始化,是否需要从父组件传递参数作为子组件的初始化成员变量。
  • build函数

所有声明在build函数的语言,在ArkTs中统称为UI描述,而UI描述有着相对严格的规则约束。

  1. @Entry装饰的自定义组件,build()函数下的根节点必须是唯一的,而且是容器组件,其中ForEach禁止作为根节点。
    @Component装饰的自定义组件,其build()函数下的根节点也必须是唯一的,可以是非容器组件,同样禁止ForEach作为根节点。
import { DemoComponent } from './demo/Demo1'
@Entry
@Component
struct Index {
  @State message: string = 'Hello World'

  build() {
    // 根节点是容器组件且唯一的
    Row() {
      DemoComponent()
      Text("测试")
    }
    .height('100%')
  }
}

@Component
export struct Child {

  build() {
    Text("这是子组件")
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  1. 不允许声明本地变量,错误示例如下。
...
build() {
    let a: string = '错误示例';
}
  • 1
  • 2
  • 3
  • 4
  1. 不允许在UI描述里直接使用console打印信息,但可以在方法或者函数里使用。
@Component
export struct Child {

  build() {
    console.log("打印信息"); // 报错提示: console.log' does not comply with the UI component syntax. <etsLint>
    Text("这是子组件")
      .onClick(() => {
        console.log("正确使用");
      })
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  1. 不允许创建本地作用域,如下
...
build() {
    {
        ...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 不允许调用方法体或返回值是组件(自定义或内置)以及方法体为空的且没有@Builder装饰的方法,但允许内置组件的参数是TS方法的返回值。
@Component
export struct Child {
  @State  count: number = 0;
  
  @Builder doSomethings() {
    Text("111111")
  }
  
  getTextVal(): string {
    return 'value';
  }
  
  hasNothing() {
    
  }

  build() {
    // 错误示例
    this.hasNothing()
    
    // 正确使用
    this.doSomethings()
    Text(this.getTextVal())
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  1. 不允许使用Switch、表达式,如需判断可以使用if
@Component
export struct Child {
  @State  count: number = 0;

  build() {
    // 错误示例
    switch(count) {
      case 1:
        Text("1")
        break;
      case 2:
        Button("2")
        break;
    }
    // 错误示例
    this.count > 10 ? Text("111") : Button("222")
    
    // 正确示例
    if (this.count == 0) {
      Text("正确操作")
    } else {
      Image("https://这是一张图片")
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
1.3.3 组件的生命周期

ArkTs的生命周期,分为页面和自定义组件的生命周期,有相同之处也有区别的地方

二者的关系:

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

下面我们简单了解一下页面生命周期:

组件的生命周期函数声明在源码的common.d.ts文件下,可以自行去查看

在这里插入图片描述

  • onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景,仅@Entry装饰的自定义组件生效
// Index.ets
import { DemoComponent } from './demo/Demo1'
@Entry
@Component
struct Index {
  // 该钩子函数触发了
  onPageShow() {
    console.log("=====Index pageShow=====");
  }

  build() {
    Row() {
      DemoComponent()
    }
    .height('100%')
  }
}

// DemoComponent.ets
@Component
export struct DemoComponent {
  @State btnText: string = 'Hello World'
  
  // 该钩子函数没有触发
  onPageShow() {
    console.log("======demo1 pageShow======");
  }

  build() {
    Row() {
      Button("按钮")
        .width(120)
        .height(40)
    }
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

在这里插入图片描述

  • onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景,仅@Entry装饰的自定义组件生效。
  • onBackPress:当用户点击返回按钮时触发,仅@Entry装饰的自定义组件生效。

这里使用IDE的预览模拟了一下,生效
在这里插入图片描述

组件生命周期,即一般使用@Component装饰的自定义组件的生命周期,如下:

  • aboutToAppear:组件即将出现时会调用该钩子,具体时机在为创建自定义组件的新实例后,在执行build()函数之前执行。而父组件的比子组件的先执行。

在这里插入图片描述

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

在这里插入图片描述

上述生命周期流程如下所示,展示的是被@Entry装饰的组件或者页面生命周期。

在这里插入图片描述

根据上面的流程图,从自定义组件的初始创建、重新渲染和删除来简单解释一下。

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

  1. 自定义组件的创建:其实例由ArkTsUI框架创建。
  2. 初始化自定义组件的成员变量:通过本地默认值或者构造方法参数来初始化组件的成员变量,初始化顺序为定义成员变量的顺序。
  3. 成员变量初始化结束,如果定义了aboutToAppear钩子函数,则执行该钩子函数
  4. 在首次渲染的时候,build方法渲染内置组件,如果子组件为自定义组件,则创建自定义组件实例。在执行build()函数过程中,框架会观察每个状态变量的读取状态,将保存两个map (具体可查看源码):
    a.状态变量 -> UI组件(包括ForEach和if)
    b.UI组件 -> 此组件的更新函数,即一个lambda方法,作为build()函数的子集,创建对应的UI组件并执行其属性方法,示例:
build() {
    ...
    this.observeComponentCreation(() => {
        Button.create();
    })
    
    this.observeComponentCreation(() => {
        Text.create();
    })
    
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

当应用在后台启动时,此时应用并没有被销毁,所以仅需要执行onPageShow钩子函数。

自定义组件重新渲染

当组件中的状态变量发生改变的时,或者localStorage/AppStorage中的属性发生变更而导致绑定的状态变量改变时:

  1. 框架观察到了变化,则会触发组件重新渲染
  2. 根据初次渲染时存储的两个map,框架可以知道该状态变量管理了哪些UI组件,以及这些UI组件对应的更新函数。执行这些需要变更的更新函数,从而实现最小化更新。

自定义组件的删除

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

  1. 在删除组件之前,将先调用aboutToDisappear钩子函数,标记着该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解开引用,前端节点已经没有引用时,将被JS虚拟机垃圾回收。
  2. 自定义组件和它的变量将被删除,如果有同步的变量,比如@Link、@Prop、@StorageLink,将从同步源上取消注册。

自定义组件的生命周期函数中还有几个不算太常用(个人意见,勿喷)的

  • pageTransition:实现页面跳转间的动画效果
  • onLayout:自定义组件中,该钩子函数可以用来覆盖其子组件的布局。该API将从API 10 开始被onPlaceChildren替换(疑似社区的官方回答)
  • onMeasure:自定义组件中,该钩子函数可以用来测量(计算)其子组件大小。该API将从API 10 开始被onMeasureSize替换(疑似社区的官方回答)

笔者目前也还没搞清楚onLayout和onMeasure两个的具体使用,社区上有人说是跟安卓的layout类似,笔者不会安卓,安卓同学方便可以告知一下。

下面是简单的示例:

// 官方示例 
// 定义一个CustomLayout组件
@Component
export struct CustomLayout {
  @BuilderParam builder: () => void

  //
  onLayout(children: Array<LayoutChild>, constraint: ConstraintSizeOptions) {
    let pos = 0;
    children.forEach((child) => {
      child.layout({ position: { x: pos, y: pos }, constraint: constraint })
      child.borderInfo.borderWidth = { top: 2, left: 2, right: 2, bottom: 2 }
      pos += 100;
    })
  }

  onMeasure(children: Array<LayoutChild>, constraint: ConstraintSizeOptions) {
    let size = 100;
    children.forEach((child) => {
      child.measure({ minHeight: size, minWidth: size, maxWidth: size, maxHeight: size })
      size += 50;
    })
  }

  build() {
    this.builder()
  }
}

// DemoIndex.ets 中使用
import { CustomLayout } from './CustomLayout'
@Entry
@Component
struct DemoIndex {
  build() {
    Row() {
      Column() {
        Button("按钮")
        .width("100%")
        .height(50)
        .backgroundColor(Color.Blue)

        CustomLayout() {
          ForEach([1,2,3], index => {
            Text("ArkTs " + index)
              .fontSize(30)
              .fontColor(Color.Red)
              .backgroundColor(Color.Black)
          })
        }
      }
    }
    .height("100%")
    .width("100%")
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

预览效果图:看结构剖析,显示内容跟结构节点(虚拟框框)不在同一位置

在这里插入图片描述

1.3.4 应用生命周期

在这里插入图片描述

1.4 常用装饰器
1.4.1 @Builder装饰器:自定义构建函数

前面我们了解了自定义组件的创建及其基本结构的理解。除了一些固定的结构之外,ArkUI还提供了一种更轻量的UI元素复用机制@Builder,被@Builder修饰的函数遵循build()函数语法规则,我们可以把一些重复的UI结构抽象到一个方法,方便在build()函数中调用。

@Builder的简单使用,如下:

  • 自定义组件内使用

语法:

@Builder myFnc(){...}
  • 1
@Component
struct DemoBuilder {

  // 自定义构建函数
  @Builder showRepeatCode() {
    Text("重复UI调用")
      .fontColor(Color.Red)
      .fontSize(12)
  }

  build() {
    this.showRepeatCode()
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  1. 自定义组件内允许有一个或者多个@Builder方法,这些方法被默认为是组件的私有、特殊类型的成员函数。
  2. 自定义构建函数不允许在所属组件外调用,可以在所属组件的build方法或者其他自定义构建函数中使用。
  3. 在自定义构建函数中,this指向当前所属组件。
  • 全局自定义构建函数

语法:

@Builder
function myGlobalBuilder(){...}
  • 1
  • 2

下面用一个简单的例子说明一下:

// 全局定义自定义构建函数 builder.ets

@Builder
export function myGlobalBuilder() {
  Column(){
    Text("全局Builder")
      .fontColor(Color.Blue)
      .fontSize(30)
  }
    .height(100)
    .width(300)
    .border({ width: 2, color: Color.Black })
}

// 调用组件 DemoBuilder.ets
import { myGlobalBuilder } from '../../utils/builder'
@Component
export struct DemoBuilder {

  // 自定义构建函数
  @Builder showRepeatCode() {
    Text("重复UI调用")
      .fontColor(Color.Red)
      .fontSize(30)
  }

  build() {
    Column() {
      this.showRepeatCode()
      myGlobalBuilder() // 全局自定义构建函数
    }
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

在这里插入图片描述

由上述例子我们可以得出以下两点:

  1. 全局的自定义构建函数可以被整个应用使用,不能使用this和bind方法
  2. 单纯的静态展示可以直接使用全局自定义构建函数,不建议传参,如果涉及状态变更,推荐使用自定义组件。

参数传递规则

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

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

1. 按引用传递参数

按引用类型传递参数时,传递参数可以是状态变量,变量值的变化也会引起@Builder方法内的UI更新。ArkUI提供$$作为按引用传递参数的范式。

语法方式:

BuilderDemo($$: {param1: string, param2: string});
  • 1

示例1:官方范式

@Builder function ABuilder($$: { paramA1: string }) {
  Row() {
    Text(`UseStateVarByReference: ${$$.paramA1} `)
  }
}
@Entry
@Component
struct Parent {
  @State label: string = 'Hello';
  build() {
    Column() {
      // 在Parent组件中调用ABuilder的时候,将this.label引用传递给ABuilder
      ABuilder({ paramA1: this.label })
      Button('Click me').onClick(() => {
        // 点击“Click me”后,UI从“Hello”刷新为“ArkUI”
        this.label = 'ArkUI';
      })
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

示例2:自定义类型


interface ItemList {
  id: number,
  name: string,
  value?: string
}

@Builder function ItemCard(item: ItemList) {
  Row() {
    Text(item.name)
      .fontColor(Color.White)
      .width(120)
    if (item.value) {
      Text(item.value)
        .fontColor(Color.Blue)
        .fontSize(30)
    }
  }
  .justifyContent(FlexAlign.SpaceBetween)
}

@Component
export struct ListBuilder {

  @State list: Array<ItemList> = [
    { id: 1, name: '测试1', value: 'hello' },
    { id: 2, name: '发送接口' },
    { id: 5, name: '数据库的费' }
  ]

  build() {
    Column() {
      List({ space: 20 }) {
        ForEach(
          this.list,
          item => {
            ListItem() {
              ItemCard(item)
            }
              .width("100%")
              .height(60)
              .padding(10)
              .backgroundColor("#ff6602")
          }
        )
      }

      Button("添加一行")
        .onClick(() => {
          const item: ItemList = this.list[this.list.length - 1];
          this.list.push({
            id: item.id + 1,
            name: "增加一行" + item.id + 1,
            value: item.id % 3 === 0 ? "add row" + item.id : ""
          })
        })
        .margin({ top: 30 })
        .width("100%")
        .height(45)
        .fontSize(22)
        .fontColor(Color.White)
        .backgroundColor(Color.Blue)
    }
      .width("100%")
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

2. 按值传递参数

调用@Builder装饰的函数默认按值传递,当传递的参数为状态变量的时,变量值的改变并没有触发组件的更新。所以传递参数使用状态变量的时候,推荐使用按引用传递。

示例:

@Builder function updateByValue(value: number) {
  Row() {
    Text("the number is " + value)
      .fontSize(20)
      .fontColor(Color.White)
  }.width("100%").height(30).backgroundColor("#ff6602")
}

@Component
export struct ValueBuilder {
  @State num: number = 10;

  build() {
    Column() {
      updateByValue(this.num)

      Button("添加一行")
        .onClick(() => {
          this.num = 100;
        })
        .margin({ top: 30 })
        .width("100%")
        .height(45)
        .fontSize(22)
        .fontColor(Color.White)
        .backgroundColor(Color.Blue)
    }
      .width("100%")
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
1.4.2 @BuilderParam装饰器

作为开发者的我们,在创建一个自定义组件的时候,而该组件承载的是公共的适应多页面使用的,当需要在该组件内设置某个页面需要的特定功能时,我们不能直接在组件内部去设置,这样不仅会导致组件更加臃肿,而且会导致其它使用该组件的地方增加了本来不需要的功能。

为了解决这种问题,ArkUI引入了@BuilderParam装饰器,用来装饰执行@Builder方法的变量,这样开发者就可以根据需要,在自定义组件初始化前传递自己需要的属性,设置特定的功能点。这个装饰器的作用就类似前端开发中的占位符slot。

装饰器使用说明

根据官方文档,@BuilderParam装饰的方法只能被自定义构建函数 (@Builder装饰的方法) 初始化。

下面通过代码展示一下:

  • 使用所属自定义的自定义构建函数或全局自定义构建函数,在本地初始化@BuilderParam
  • 父组件自定义构建函数初始化子组件自定义@BuilderParam装饰的方法
// 编写BuilderParamDemo.ets
@Builder function WholeApp1() {
  Text("===WholeApp1===")
}

@Component
export struct BuilderParamDemo {
  @Builder doNothing() {}

  @BuilderParam builderParam1: () => void = WholeApp1;
  @BuilderParam builderParam2: () => void = this.doNothing;

  build() {
    Column() {
      this.builderParam1()
      Row().margin({ top: 20, bottom: 20 })
      this.builderParam2()
    }
  }
}
// 在Index.ets中使用
import { BuilderParamDemo } from './demo/BuilderParamDemo';

@Entry
@Component
struct Index {
  @Builder updateBuilderParam() {
    Button("测试")
      .width("100%")
      .height(60)
      .backgroundColor(Color.Brown)
  }

  build() {
    Row() {
      // 无参
      // BuilderParamDemo()
      
      // 有参--父组件自定义构建函数初始化子组件自定义@BuilderParam装饰的方法
      BuilderParamDemo({
        builderParam2: this.updateBuilderParam
      })
    }
    .height('100%')
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

在这里插入图片描述

下面来个综合的例子,演示一下@BuilderParam装饰的自定义构建函数在以下两个场景中的使用:

  • 参数初始化组件
  • 尾随闭包初始化组件

@Builder function WholeBuilder($$: { value: string }) {
  Text($$.value)
    .fontSize(30)
    .fontColor(Color.Red)
}

@Entry
@Component
struct BuilderIndex {
  childVal: string = "parent component"

  @Builder ParentBuilder($$: { value: string }) {
    Text($$.value)
      .fontColor(Color.Black)
      .fontSize(30)
  }

  build() {
    Column(){
      // 场景1:通过参数初始化
      ChildIndex({ showItem: WholeBuilder })
      Divider()
      ChildIndex({ showItem: WholeBuilder, title: "BuilderIndex" })

      // 场景2:尾随闭包初始化组件
      // 该场景下自定义组件内有且只有一个使用@BuilderParam装饰的属性
      Divider()
      ChildIndex() {}

      Divider()
      ChildIndex({ title: "测试场景2-2" }) {}

      Divider()
      ChildIndex({ title: "测试场景2-3" }) {
        this.ParentBuilder({ value: "the parent Builder" })
      }

      Divider()
      ChildIndex({ title: "测试场景2-4" }) {
        Button("Parent button")
          .width(300)
          .height(60)
          .fontSize(26)
          .backgroundColor("#ff6602")
      }
    }
      .width("100%")
      .height("100%")
  }
}


@Component
struct ChildIndex {
  @State title: string = "ChildIndex"
  @BuilderParam showItem: ($$: { value: string | number }) => void

  build() {
    Row() {
      Column() {
        Text(this.title)
          .fontColor(Color.Blue)
          .fontSize(30)
          .margin({ bottom: 20 })
        this.showItem({ value: "default value" })
      }
      .width("100%")
    }
    .margin({ top: 10, bottom: 10 })
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

在这里插入图片描述

当我们把ChildIndex中@BuilderParam装饰的自定义构建函数注释掉,不在build()函数执行,在parent组件中使用的效果如下图,发现slot效果消失了,所以定义之后的@BuilderParam构建函数一定要在build中执行才有效果。

在这里插入图片描述

1.5 样式装饰器

样式是应用开发中重要的一环,几乎每个组件都必须要设置,也不可避免的出现大量重复的样式代码,这样不仅造成单个文件内代码量过多,而且不容易维护,不容易扩展等问题,所以ArkUI给开发者提供了相应的装饰器解决问题。

1.5.1 @Styles装饰器

@Styles装饰器可以把多条样式写在一个方法,可以在组件需要的地方调用即可。下面是该装饰器支持的与限制的用法

  • 目前@Styles仅支持通用属性通用事件
  • @Styles方法不支持参数
  • @Styles可以定义在组件内或全局,在全局定义时需要在方法名前添加function关键字,组件内定义则不需要(跟@Builder类似)。
  • 定义在组件内的@Styles可以访问组件的常量和状态变量,还可以通过事件来改变状态变量的值。
  • 组件内的@Styles的优先级高于全局的。

通过下面的代码,我们熟悉一下@Styles


// 全局样式
@Styles function GlobalStyles() {
  .width("100%")
  .height("100%")
  .backgroundColor("#f8f8f8")
}

@Entry
@Component
struct SytlesIndex {
  @State tColor: string = "#FF164FFA"

  // 组件内定义
  @Styles txt_cls() {
    .width("100%")
    .height(60)
    .backgroundColor("#ff6602")
    .onClick(() => {
      this.tColor = "#ff6602"
    })
  }

  @Styles txt() {
    .width("100%")
    .height(50)
    .backgroundColor(this.tColor)
  }

  build() {
    Column() {
      Text("这是一行文本")
        .txt() // 使用组件内定义样式
        .fontSize(30)
        .fontColor(Color.White)
        .margin({ bottom: 20 })
      Button("change color")
        .txt_cls() // 使用组件内定义样式
        .fontSize(30)
    }
    .GlobalStyles() // 定义的全局样式
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

总结:@Styles可以提取大部分的样式,但是发现文本类型的样式没办法提取(希望我没有用错),总体上还是很方便的,有点可惜就是不能传参。

1.5.2 @Extend装饰器

@Extend装饰器是在@Styles的基础上,为开发者提供扩展原生(内置)组件样式的能力。

@Extend语法:

@Extend(UI组件名) function fncName() {...}
  • 1

使用@Extend装饰器需要遵循的规则:

  • @Extend只支持全局定义,不能在组件内部定义
  • @Extend支持封装指定的组件的私有属性和私有事件和预定义相同组件的@Extend的方法
  • 与@Styles不同的是,@Extend装饰的方法是可以传递参数的,同时,@Extend也可以支持文本类型的样式属性
  • @Extend装饰的方法的参数可以是function,作为Event 事件的句柄
  • @Extend的参数如果是状态变量,当变量的值发生改变,则会触发UI的刷新
// 只能扩展内置组件,不能扩展自定义组件
// @Extend(Demo) function demoFnc() {}

@Extend(Row) function rowCls() {
  .width("100%")
  .height(60)
  .margin({ bottom: 10 })
  .backgroundColor("#ff6602")
}

@Extend(Row) function oddCls() {
  .rowCls() // 调用预定义的rowCls
  .backgroundColor(Color.Blue)
}

// 支持文本css属性
@Extend(Text) function txtCls($$: { color: string | Color }) {
  .fontSize(30)
  .fontColor($$.color)
}

@Entry
@Component
struct ExtendIndex {
  @State newColor: string | Color = "#000"

  changeColor = () => {
    this.newColor = Color.Red
  }

  build() {
    Column() {
      Row() {
        Text("first row")
          .txtCls({ color: "#fff" })
      }
      .rowCls()
      Row() {
        Text("odd row")
          .txtCls({ color: this.newColor }) // 传递参数:状态变量
      }
      .oddCls()
      Row() {
        Button("change color")
          .fontSize(30)
          .onClick(this.changeColor)
      }
    }
  }
}

@Component
struct Demo {
  build() {
    Column() {
      Text("demo text")
        .fontSize(30)
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

注意:目前ArkTs并不支持@Extend、@Styles的跨文件使用,不能使用export 导出,如果有其他跨文件使用,可以告知一下。

1.5.3 stateStyles 多态样式

@Styles和@Extend作用于静态页面的样式复用,而stateStyles可以依据组件的内部状态的不同,快速设置不同的样式。

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

  • focused:聚焦状态
  • normal:正常状态
  • pressed:按压状态
  • disabled:禁用状态

下面是示例:


@Entry
@Component
struct StateStylesIndex {

  @State inputTxt: string = "";

  @Styles normalCls() {
    .width("100%")
    .height(60)
    .backgroundColor(Color.Orange)
  }

  build() {
    Column() {
      Button("按钮")
        .onClick(() => {
          this.inputTxt = "点击了按钮";
        })
        .margin({ bottom: 30, top:5 })
        .fontSize(30)
        .stateStyles({
          normal: this.normalCls,
          pressed: {
            .backgroundColor(Color.Blue)
            .fontColor(Color.White)
          }
        })

      TextInput({
        placeholder: "请输入内容",
        text: this.inputTxt
      })
        .width("90%")
        .height(50)
        .fontSize(25)
        .stateStyles({
          normal: {
            .fontColor("#999999")
            .borderWidth(2)
            .borderColor(Color.Gray)
          },
          focused: {
            .borderWidth(2)
            .borderColor(Color.Blue)
          }
        })
    }
    .width("100%")
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

总结:以上文章的介绍,让我们对ArkTs有了基本的了解,知道了如何去构建页面和自定义组件,后续,我们将去学习它的状态管理等知识点。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/221998
推荐阅读
相关标签
  

闽ICP备14008679号