鸿蒙开发-ArkTS 语言-基础语法

[写在前面: 文章多处用到gif动图,如未自动播放,请点击图片]

1. 初识 ArkTS 语言

ArkTS 是 HarmonyOS 优选主力开发语言。ArkTS 是基于 TypeScript (TS) 扩展的一门语言,继承了 TS 的所有特性,是TS的超集。

主要是扩展了以下几个方面:

  1. 声明式UI描述和自定义组件:

    • ArkTS使用声明式的方式描述用户界面(UI),布局更直观易懂。
    • 可以自定义组件
  2. 多维度的状态管理机制:

    ArkTS可以使用@State、@Prop 等装饰器以及 LocalStorage 等管理应用的状态。

    主要表现在:

    • 支持在组件内使用与UI相关联的数据,实现局部状态管理。
    • 支持在不同组件层级间传递数据,包括父子组件和爷孙组件之间的传递。
    • 全局状态管理允许在应用的整个范围内传递数据,也可以跨设备传递。
    • 提供只读的单向数据传递和可变更的双向数据传递。
  3. 渲染控制的能力:

    ArkTS提供了灵活的渲染控制功能,使开发者能够根据应用的状态动态渲染UI内容。

    主要表现在:

    • 条件渲染(if-else)允许根据不同的应用状态选择性地渲染UI内容。
    • 循环渲染支持从数据源中迭代获取数据,并在每次迭代中创建相应的组件。
    • 数据懒加载允许按需迭代数据,并在每次迭代中创建相应的组件,提高应用性能。

2. 基本语法

2.1 ArkTS的基本组成

概念 描述
装饰器 用于装饰类、结构、方法以及变量,赋予其特殊含义。例如,@Component 表示自定义组件,@Entry 表示入口组件,@State 表示状态变量。状态变量的变化会触发UI刷新。
UI描述 使用声明式的方式描述UI的结构,通常体现在 build() 方法中的代码块。
自定义组件 可复用的UI单元,可组合其他组件。被 @Component 装饰的 struct xxx 就是一个自定义组件。
系统组件 ArkUI框架中默认内置的基础和容器组件,可以直接调用。 常见系统组件:ColumnTextDividerButton
属性方法 组件可以通过链式调用配置多项属性的方法。例如,fontSize()width()height()backgroundColor() 可以用于设置组件的样式和尺寸等属性。
事件方法 组件可以通过链式调用设置多个事件的响应逻辑。例如,在 Button 后面的 onClick() 就是一个事件方法,用于处理按钮被点击时的逻辑。

经典案例代码示例:

ts 复制代码
// 1. 按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起@Builder方法内的UI刷新
@Builder function MessageBuilder($$:{message:string}) { // 自定义装饰器
 Row(){
   Text(`Message is ${$$.message}`)
 }
}

// 2. 按值传递
@Builder function ValueBuilder(message : string) {
  Row() {
    Text(`message is ${message}`)
  }
}

@Entry // 装饰器
@Component // 装饰器
struct Index { // 使用 struct 关键字定义组件
  @State message: string = 'hello' // @State表示组件中的状态变量,状态变量变化会触发UI刷新
  @State count: number = 1
  build() {
    Row() { // 行
      Column() { // 列
        Text(this.message + this.count)
          .fontSize(50)  // 属性方法
          .fontWeight(FontWeight.Bold)

        Button('点我试试')
          .onClick(() => {
            this.count ++
            this.message = '你好'
          })
          .fontColor('#000')

        MessageBuilder({message: this.message}) // 传递参数的引用

        ValueBuilder(this.message) // 按值传递,无响应式
      }

      .width('100%')
    }
    .height('100%')
  }
}

效果如下:

2.2 声明式 UI

ArkTS采用声明方式组合和扩展组件来描述应用程序的UI,同时提供了基本的属性、事件和子组件配置方法

1. 创建组件:

  • 无参数创建:对于没有必选构造参数的组件,可以直接在组件后面使用空括号,例如 Divider()

  • 有参数创建:如果组件包含构造参数,可以在括号内配置相应参数,例如 Text('你好')

    ts 复制代码
    Column() {
      Text('item 1')
      Divider()
      Text('item 2')
    }

2. 配置属性:

  • 使用属性方法通过"."链式调用配置系统组件的样式和其他属性,建议每个属性方法单独写一行,如设置字体大小、宽度、高度等。

    ts 复制代码
    Text('test')
      .fontSize(12)

3. 配置事件:

  • 通过事件方法以"."链式调用配置系统组件支持的事件,可使用lambda表达式、匿名函数表达式或组件的成员函数来定义

    ts 复制代码
    Button('Click me')
      .onClick(() => {
        this.myText = 'ArkUI';
      })

2.3 自定义组件

在ArkUI中,UI显示的内容由组件构成,其中框架直接提供的组件称为系统组件而开发者定义的组件则被称为自定义组件

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

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

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

  3. 数据驱动UI更新: 通过状态变量的改变来驱动UI的刷新。

自定义组件的结构:

概念 描述
struct 基于 struct 实现自定义组件,结构为 struct + 自定义组件名 + {...}。实例化时可以省略 new
@Component 装饰器,仅能装饰使用 struct 关键字声明的数据结构。被装饰后的结构具备组件化能力,需要实现 build 方法描述UI。一个 struct 只能被一个 @Component 装饰。
build() 函数 用于定义自定义组件的声明式 UI 描述。自定义组件必须实现 build() 函数。
@Entry 装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用一个 @Entry 装饰的自定义组件。可以接受一个可选的 LocalStorage 参数。

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

ts 复制代码
@Component
struct GreetingComponent {
  @State greeting: string = 'Hello, World!';

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

GreetingComponent 可以在其他自定义组件的 build() 函数中多次创建,实现自定义组件的重用。

ts 复制代码
@Entry
@Component
struct ParentComponent {
  build() {
    Column() {
      Text('ArkUI Greetings');
      GreetingComponent({ greeting: 'Hello, World!' });
      Divider();
      GreetingComponent({ greeting: '你好!' });
    }
  }
}

成员函数/变量

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

  • 不支持静态函数。
  • 成员函数的访问始终是私有的。

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

  • 不支持静态成员变量。
  • 所有成员变量都是私有的,变量的访问规则与成员函数的访问规则相同。
  • 自定义组件的成员变量本地初始化有些是可选的,有些是必选的。具体是否需要本地初始化,是否需要从父组件通过参数传递初始化子组件的成员变量,请参考官方文档:状态管理

build()函数

@Entry装饰的自定义组件,必须要有且仅有一个 build() 函数,且必须为容器组件,其中ForEach禁止作为根节点,他会产生多节点。

@Component装饰的自定义组件,其build()函数下的根节点唯一且必要,可以为非容器组件,其中ForEach禁止作为根节点。

代码示例:

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

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

build() 函数内,不允许以下几点:

ts 复制代码
build() {  
    // 反例:不允许声明本地变量 
    let a: number = 1;

    // 反例:不允许console.info
    console.info('print debug log');

    // 反例:不允许本地作用域
    {
        ...
    }
        //   不允许switch语法,如果需要使用条件判断,请使用if。反例如下。
        switch (expression) {
            case 1:
                Text('...')
                break;
            case 2:
                Image('...')
                break;
            default:
                Text('...')
                break;
        }
        //  不允许使用表达式,反例如下。
        (this.aVar > 10) ? Text('...') : Image('...')
    }

另外:

不允许调用除了被@Builder装饰以外的方法,允许系统组件的参数是TS方法的返回值

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())
    }
  }
}

2.4 自定义组件通用样式

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

ts 复制代码
@Component
struct MyComponent2 {
  build() {
    Button(`Hello World`)
  }
}

@Entry
@Component
struct MyComponent {
  build() {
    Row() {
      MyComponent2() // 为自定义组件添加通用样式
        .width(200)
        .height(300)
        .backgroundColor(Color.Red)
    }
  }
}

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

可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。

图示:

图片来源:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-page-custom-components-lifecycle-0000001524296665-V2

生命周期接口 描述
onPageShow 每次页面显示时触发。
onPageHide 每次页面隐藏时触发一次。
onBackPress 当用户点击返回按钮时触发。
aboutToAppear 组件即将出现时 回调,具体时机为在创建新实例后在执行 build() 函数之前执行。
aboutToDisappear 在自定义组件即将销毁时执行。

示例代码:

ts 复制代码
// 包含两个自定义组件,一个是被@Entry装饰的MyComponent,也是页面的入口组件,即页面的根节点;一个是Child,是MyComponent的子组件。
// 只有@Entry装饰的节点才可以生效页面的生命周期方法,所以MyComponent中声明了当前Index页面的页面生命周期函数。MyComponent和其子组件Child也同时也声明了组件的生命周期函数。
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('create or 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';
    })
  }
}

2.6. @Builder装饰器:自定义构建函数

定义的语法:

ts 复制代码
@Builder MyBuilderFunction({ ... })

使用方法:

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

示例如下:

ts 复制代码
@Builder function  GlobalBuilder() {
  Text('我是全局装饰器')
}

@Component
struct Children1 { // 子组件
  @Builder DoNothing(){}
  @BuilderParam aBuilder: () => void = this.DoNothing; // 定义局部的装饰器
  @BuilderParam bBuilder: () => void = GlobalBuilder; // 定义全局的装饰器

  build(){
    Column() {
      GlobalBuilder()
    }
  }
}

@Component
struct Children2 {
  @BuilderParam paramsBuilder: () => void; // 声明装饰器

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

@Component
@Entry
struct Index {
  @Builder componentBuilder() {
    Text('我是父组件的 builder')
  }
  build() {
    Column() {
      Children1()
      Children2({ paramsBuilder: this.componentBuilder}) // 传入一个 builder 给子组件
    }.width('100%')
  }
}

尾随闭包初始化组件

在初始化自定义组件时,紧跟一个大括号"{}"形成尾随闭包场景,将尾随闭包内的内容看做@Builder装饰的函数传给@BuilderParam

ts 复制代码
// 尾随闭包初始化组件
@Component
struct CustomContainer {
  @Prop header: string;
  @BuilderParam closer: () => void // 准备接受闭包参数

  build() {
    Column() {
      Text(this.header)
        .fontSize(30)
      this.closer()
    }
  }
}

@Builder function specificParam(label1: string, label2: string) {
  Column() {
    Text(label1)
      .fontSize(30)
    Text(label2)
      .fontSize(30)
  }
}

@Entry
@Component
struct Index {
  @State text: string = 'header';

  build() {
    Column() {
      // 在创建CustomContainer时,通过其后紧跟一个大括号"{}"形成尾随闭包
      // 用内部的 this.text 作为参数
      CustomContainer({ header: this.text }) {
        Column() {
          specificParam('testA', 'testB')
        }.backgroundColor(Color.Yellow)
        .onClick(() => {
          this.text = 'changeHeader';
        })
      }
    }
  }
}

效果如下:

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

如果每个组件的样式都需要单独设置,在开发过程中会出现大量代码在进行重复样式设置,虽然可以复制粘贴,但为了代码简洁性和后续方便维护,可以使用公共样式装饰器@Styles。

使用方法:

ts 复制代码
// 反例: @Styles不支持参数
// @Styles function globalFancy (value: number) {
//   .width(value)
// }

// @Styles可以定义在组件内或全局,在全局定义时需在方法名前面添加function关键字,
// 组件内定义时则不需要添加function关键字。

// 定义在组件内的@Styles可以通过this访问组件的常量和状态变量,
// 并可以在@Styles里通过事件来改变状态变量的值

// 组件内@Styles的优先级高于全局@Styles。

@Component
@Entry
struct Index  {
  @State heightValue: number = 100
  @Styles fancy() {
    .height(this.heightValue)
    .backgroundColor(Color.Yellow)
    .onClick(() => {
      this.heightValue = 200
    })
  }
  build() {
    Column(){
      Row(){
        Text('自定义样式,点我也能改变样式')
          .fancy()
      }
      Divider()
      Row(){
        Text('点我也一样,也会跟着改变样式')
          .fancy()
      }
    }
  }
}

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

使用规则

  • 和@Styles不同,@Extend仅支持定义在全局,不支持在组件内部定义。
  • 和@Styles不同,@Extend支持封装指定的组件的私有属性和私有事件,也可以预定义相同组件的@Extend的方法
ts 复制代码
// 和@Styles不同,@Extend仅支持定义在全局,不支持在组件内部定义。
// 和@Styles不同,@Extend支持封装指定的组件的私有属性和私有事件和预定义相同组件的@Extend的方法。
// 和@Styles不同,@Extend装饰的方法支持参数,开发者可以在调用时传递参数,调用遵循TS方法传值调用。
// @Extend的参数可以为状态变量,当状态变量改变时,UI可以正常的被刷新渲染
@Extend(Text) function fancy (fontSize: number) { // 只给 Text 继承了 fancy
  .fontColor(Color.Red)
  .fontSize(fontSize)
}

// @Extend装饰的方法的参数可以为function,作为Event事件的句柄。
@Extend(Text) function makeMeClick(onClick: () => void) {
  .backgroundColor(Color.Blue)
  .onClick(onClick)
}

@Entry
@Component
struct Index {
  @State label: string = 'Hello World';
  @State fontSizeValue: number = 58;
  onClickHandler() {
    this.label = 'Hello ArkUI';
    this.fontSizeValue = 108
  }
  build(){
    Column(){
      Row({space: 10}) {
        Text('测试')
          .fancy(this.fontSizeValue) // 传入可响应数据,后续函数执行,字号也会发生变化
        // Span('span无效')
        //   .fancy() // Property 'fancy' does not exist on type 'SpanAttribute'. <tsCheck>
      }
      Row({ space: 10 }) {
        Text(`${this.label}`)
          .makeMeClick(this.onClickHandler.bind(this)) // bind 绑定当前作用域
          .fancy(109)
      }
    }
  }
}

2.9 stateStyles:多态样式

ts 复制代码
// 多态样式
// stateStyles是属性方法,可以根据UI内部状态来设置样式,类似于css伪类,但语法不同。ArkUI提供以下四种状态:
//
// 1. focused:获焦态。
// 2. normal:正常态。
// 3. pressed:按压态。
// 4. disabled:不可用态。

// 基础场景
// 下面的示例展示了stateStyles最基本的使用场景。Button处于第一个组件,默认获焦,
// 生效focused指定的粉色样式。按压时显示为pressed态指定的黑色。
// 如果在Button前再放一个组件,使其不处于获焦态,就会生效normal态的黄色。

// Styles也可和stateStyles联合使用
@Entry
@Component
struct Index {
  @Styles normalStyle() {
    .backgroundColor(Color.Gray)
  }

  @Styles pressedStyle() {
    .backgroundColor(Color.Red)
  }
  build() {
    Column() {
      Button('Click me')
        .stateStyles({
          focused: {
            .backgroundColor(Color.Pink)
          },
          pressed: {
            .backgroundColor(Color.Black)
          },
          normal: {
            .backgroundColor(Color.Yellow)
          }
        })
      Column() {
        Text('Text1')
          .fontSize(50)
          .fontColor(Color.White)
          .stateStyles({
            normal: this.normalStyle,
            pressed: this.pressedStyle,
          })
      }
    }.margin('30%')
  }
}


衔接下一篇: 鸿蒙开发-ArkTS 语言-状态管理

相关推荐
Swift社区1 小时前
如何构建安全可靠的 HarmonyOS 应用
harmonyos·arkts·arkui
AI+程序员在路上1 小时前
鸿蒙系统(HarmonyOS)介绍
华为·harmonyos
lqj_本人2 小时前
鸿蒙next版开发:相机开发-录像(ArkTS)
数码相机·华为·harmonyos
23zhgjx-NanKon3 小时前
华为eNSP:MSTP
华为
bingw01143 小时前
华为机试HJ42 学英语
数据结构·算法·华为
Swift社区4 小时前
HarmonyOS 如何获取设备信息(系统、版本、网络连接状态)
华为·harmonyos
Pedestrians747 小时前
OSPF总结
网络·华为
lqj_本人11 小时前
鸿蒙next版开发:相机开发-拍照(ArkTS)
数码相机·华为·harmonyos
郝晨妤11 小时前
HarmonyOS和OpenHarmony区别是什么?鸿蒙和安卓IOS的区别是什么?
android·ios·harmonyos·鸿蒙
lqj_本人12 小时前
鸿蒙next版开发:相机开发-会话管理(ArkTS)
华为·harmonyos