【鸿蒙开发】第七章 ArkTS语言UI范式-基础语法

这篇具有很好参考价值的文章主要介绍了【鸿蒙开发】第七章 ArkTS语言UI范式-基础语法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1 前言

通过前面的章节,我们基本清楚鸿蒙应用开发用到的语言和项目基本结构,在【鸿蒙开发】第四章 Stage应用模型及项目结构也提到过ArkTS的UI范式的基本语法状态管理渲染控制等能力,简要介绍如下:

  1. 基本语法ArkTS定义了声明式UI描述自定义组件动态扩展UI元素的能力,再配合ArkUI开发框架中的系统组件及其相关的事件方法属性方法等共同构成了UI开发的主体。
  2. 状态管理ArkTS提供了多维度的状态管理机制。在UI开发框架中,与UI相关联的数据可以在组件内使用,也可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,还可以在应用全局范围内传递或跨设备传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活的利用这些能力来实现数据和UI的联动。
  3. 渲染控制ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。数据懒加载从数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。

本章节我们先来展开学习一下基本语法,后续文章会介绍到状态管理渲染控制另外两方面的详细知识。

2 基本语法

我们先来看看前面章节也出现过的这张图片,下面是新建项目后,页面的示范代码。
【鸿蒙开发】第七章 ArkTS语言UI范式-基础语法,Harmoney,ArkTS,DevEco Studio,harmonyos,openharmoney,DevEcoStudio,华为

  1. 装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如上述示例中@Entry、@Component和@State都是装饰器,@Component表示自定义组件,@Entry表示该自定义组件为入口组件,@State表示组件中的状态变量,状态变量变化会触发UI刷新。
  2. UI描述:以声明式的方式来描述UI的结构,例如build()方法中的代码块。
  3. 自定义组件:可复用的UI单元,可组合其他组件,如上述被@Component装饰的struct Hello。
  4. 系统组件:ArkUI框架中默认内置的基础和容器组件,可直接被开发者调用,比如示例中的Column、Text、Divider、Button。
  5. 属性方法:组件可以通过链式调用配置多项属性,如fontSize()、width()、height()、backgroundColor()等。
  6. 事件方法:组件可以通过链式调用设置多个事件的响应逻辑,如跟随在Button后面的onClick()。
    系统组件、属性方法、事件方法具体使用可参考基于ArkTS的声明式开发范式。
  7. @Builder/@BuilderParam:特殊的封装UI描述的方法,细粒度的封装和复用UI描述。
  8. @Extend/@Style:扩展内置组件和封装属性样式,更灵活地组合内置组件。
  9. stateStyles:多态样式,可以依据组件的内部状态的不同,设置不同样式。

2.1 声明式UI

通过前面章节,我们知道ArkTS声明方式组合和扩展组件来描述应用程序的UI,同时还提供了基本的属性事件子组件配置方法,帮助开发者实现应用交互逻辑。

2.1.1 组件创建

根据组件构造方法的不同,创建组件包含有参数无参数两种方式。创建组件时不需要new运算符。

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

// Image的参数为必选参数
Image('https://xyz/test.jpg')
// Text为可选参数,有无参数都可
Text($r('app.string.title_value'))
Text()

2.1.2 配置属性

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

属性方法中参数根据定义,传递常量参数变量参数枚举类型表达式

Text('hello')
  .width(this.count % 2 === 0 ? 100 : 200)    
  .height(this.offset + 100)
  .fontSize(20)
  .fontColor(Color.Red)
  .fontWeight(FontWeight.Bold)

2.1.3 配置事件

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

Button('add counter')
  .onClick(() => {
    this.counter += 2;
  })

// 使用声明的箭头函数,可以直接调用,不需要bind this。
fn = () => {
  console.info(`counter: ${this.counter}`)
  this.counter++
}
...
Button('add counter')
  .onClick(this.fn)

2.1.4 配置子组件

如果组件支持子组件配置,则需在尾随闭包"{…}"中为组件添加子组件的UI描述。ColumnRowStackGridList等组件都是容器组件

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

2.2 自定义组件

自定义组件即开发者根据自己的业务要求定义组件,代码可复用性、业务逻辑与UI分离,后续版本演进等因素。因此,将UI和部分业务逻辑封装成自定义组件是不可或缺的能力。

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

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

下面我们以一个简单的自定义组件案例来学习:

@Entry
@Component
export 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!';
        })
    }
  }
}

@Entry
@Component
struct ParentComponent {

  build() {
    Column() {
      // 创建HelloComponent实例,并初始化成员变量message
      MyComponent({ message: 'Hello,World!My name is Yvan' })
    }
  }
}
  1. export:如果在另外的文件中引用该自定义组件,需要使用export关键字导出,并在使用的页面import该自定义组件。
  2. struct:自定义组件基于struct实现,struct + 自定义组件名 + {…}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。
  3. @Component:仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的能力,需要实现build方法描述UI,一个struct只能被一个@Component装饰。
  4. build():build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。
  5. @Entry:@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件。@Entry可以接受一个可选的LocalStorage的参数。(从API version 10开始,@Entry可以接受一个可选的LocalStorage的参数或者一个可选的EntryOptions参数。)
名称 类型 必填 说明
routeName string 表示作为命名路由页面的名字。
storage LocalStorage 页面级的UI状态存储。
@Entry({ routeName : 'myPage' })
@Component
struct MyComponent {
}

  1. @Reusable:@Reusable装饰的自定义组件具备可复用能力
@Reusable
@Component
struct MyComponent {
}

2.2.1 成员函数/变量

自定义组件中函数和变量,都具备以下约束:

  1. 不支持静态
  2. 访问私有化

而变量具体是否需要本地初始化,是否需要从父组件通过参数传递初始化子组件的成员变量,具体后续文章状态管理的内容会介绍。

2.2.2 build()函数

所有声明在build()函数的语言,我们统称为UI描述。
我们先来看看这个案例:

@Entry
@Component
struct MyComponent {
  @State count: number = 1;

  build() {
    // 1.根节点唯一且必要,必须为容器组件
    Row() {
      ChildComponent() 
    }
  	// 2.不允许声明本地变量
  	// let a: number = 1;
  	
  	// 3.不允许console.info
  	// console.info('print debug log');

  	// 4.不允许本地作用域
  	//{
    //	...
  	//}
  	
    // 5.不能调用没有用@Builder装饰的方法
    // this.doSomeCalculations();
    // 可以调用
    // this.doSomeRender();
    // 参数可以为调用TS方法的返回值
    // Text(this.calcTextValue())

    // 6.不允许使用switch语法
    //switch (expression) {
    //  case 1:
    //    Text('...')
    //    break;
    //  default:
    //    Text('...')
    //    break;
    //}
    
 	// 7.不允许使用表达式
    // (this.aVar > 10) ? Text('...') : Image('...')

    // 8. 应避免直接在Text组件内改变count的值
    // Text(`${this.count++}`)
  }
  
  doSomeCalculations() {
  }

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

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

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

UI描述需要遵循以下规则:

  1. @Entry装饰的自定义组件,其build()函数下的根节点唯一且必要,且必须为容器组件,其中ForEach禁止作为根节点。 @Component装饰的自定义组件,其build()函数下的根节点唯一且必要,可以为非容器组件,其中ForEach禁止作为根节点。
  2. 不允许声明本地变量
  3. 不允许在UI描述里直接使用console.info,但允许在方法或者函数里使用
  4. 不允许创建本地的作用域
  5. 不允许调用没有用@Builder装饰的方法,允许系统组件的参数是TS方法的返回值
  6. 不允许switch语法,如果需要使用条件判断,请使用if
  7. 不允许使用表达式
  8. 不允许直接改变状态变量

其中,第8点需要注意。在ArkUI状态管理中,状态驱动UI更新。所以,不能在自定义组件的build()@Builder方法里直接改变状态变量,这可能会造成循环渲染的风险。Text(‘${this.count++}’)全量更新最小化更新会产生不同的影响。
build函数中更改应用状态的行为可能会比上面的示例更加隐蔽,如:

  1. 在@Builder,@Extend或@Styles方法内改变状态变量 。
  2. 在计算参数时调用函数中改变应用状态变量,例如 Text(‘${this.calcLabel()}’)。
  3. 对当前数组做出修改,sort()改变了数组this.arr,随后的filter方法会返回一个新的数组。
// 反例
@State arr : Array<...> = [ ... ];
ForEach(this.arr.sort().filter(...), 
  item => { 
  ...
})
// 正确的执行方式为:filter返回一个新数组,后面的sort方法才不会改变原数组this.arr
ForEach(this.arr.filter(...).sort(), 
  item => { 
  ...
})

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

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

  1. 自定义组件@Component装饰的UI单元,可以组合多个系统组件实现UI的复用,可以调用组件的生命周期。

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

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

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

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

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

生命周期流程如下图所示,下图展示的是被@Entry装饰的组件(首页)生命周期
【鸿蒙开发】第七章 ArkTS语言UI范式-基础语法,Harmoney,ArkTS,DevEco Studio,harmonyos,openharmoney,DevEcoStudio,华为

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

  1. 自定义组件的创建:自定义组件的实例由ArkUI框架创建。

  2. 初始化自定义组件的成员变量:通过本地默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序。

  3. 如果开发者定义了aboutToAppear,则执行aboutToAppear方法。

  4. 在首次渲染的时候,执行build方法渲染系统组件,如果子组件为自定义组件,则创建自定义组件的实例。在执行build()函数的过程中,框架会观察每个状态变量的读取状态,将保存两个map:

    1. 状态变量 -> UI组件(包括ForEach和if)
    2. UI组件 -> 此组件的更新函数。即一个lambda方法,作为build()函数的子集,创建对应的UI组件并执行其属性方法,示意如下。
build() {
  ...
  this.observeComponentCreation(() => {
    Button.create();
  })

  this.observeComponentCreation(() => {
    Text.create();
  })
  ...
}

当应用在后台再次启动时,此时应用进程并没有销毁,所以仅需要执行onPageShow

2.3.2 自定义组件重新渲染

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

  1. 框架观察到了变化,将启动重新渲染。

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

2.3.3 自定义组件的删除

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

  1. 在删除组件之前,将调用其aboutToDisappear生命周期函数,标记着该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,前端节点已经没有引用时,将被JS虚拟机垃圾回收。

  2. 自定义组件和它的变量将被删除,如果其有同步的变量,比如@Link、@Prop、@StorageLink,将从同步源上取消注册。

不建议在生命周期aboutToDisappear内使用async await,如果在生命周期的aboutToDisappear使用异步操作(Promise或者回调方法),自定义组件将被保留在Promise的闭包中,直到回调方法被执行完,这个行为阻止了自定义组件的垃圾回收。

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

// 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';
    })
  }
}

// page.ets
@Entry
@Component
struct page {
  @State textColor: Color = Color.Black;
  @State num: number = 0

  onPageShow() {
    this.num = 5
  }

  onPageHide() {
    console.log("page onPageHide");
  }

  onBackPress() { // 不设置返回值按照false处理
    this.textColor = Color.Grey
    this.num = 0
  }

  aboutToAppear() {
    this.textColor = Color.Blue
  }

  build() {
    Column() {
      Text(`num 的值为:${this.num}`)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
        .fontColor(this.textColor)
        .margin(20)
        .onClick(() => {
          this.num += 5
        })
    }
    .width('100%')
  }
}

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

  1. 应用冷启动的初始化流程为:MyComponent aboutToAppear --> MyComponent build --> Child aboutToAppear --> Child build --> Child build执行完毕 --> MyComponent build执行完毕 --> Index onPageShow

  2. 点击“delete Child”,if绑定的this.showChild变成false,删除Child组件,会执行Child aboutToDisappear方法。

  3. 点击“push to next page”,调用router.pushUrl接口,跳转到另外一个页面,当前Index页面隐藏,执行页面生命周期Index onPageHide。此处调用的是router.pushUrl接口,Index页面被隐藏,并没有销毁,所以只调用onPageHide。跳转到新页面后,执行初始化新页面的生命周期的流程。

  4. 如果调用的是router.replaceUrl,则当前Index页面被销毁,执行的生命周期流程将变为:Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。上文已经提到,组件的销毁是从组件树上直接摘下子树,所以先调用父组件的aboutToDisappear,再调用子组件的aboutToDisappear,然后执行初始化新页面的生命周期流程。

  5. 点击返回按钮,触发页面生命周期Index onBackPress,且触发返回一个页面后会导致当前Index页面被销毁

  6. 最小化应用或者应用进入后台,触发Index onPageHide。当前Index页面没有被销毁,所以并不会执行组件的aboutToDisappear。应用回到前台,执行Index onPageShow

  7. 退出应用,执行Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear文章来源地址https://www.toymoban.com/news/detail-770028.html

到了这里,关于【鸿蒙开发】第七章 ArkTS语言UI范式-基础语法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 『C语言初阶』第七章 -初识指针

    🔥 博客主页 : 小羊失眠啦. 🔖 系列专栏 : C语言 🌥️ 每日语录 : 苦难是化了妆的祝福。 ❤️ 感谢大家点赞👍收藏⭐评论✍️ 时隔多日小羊又来给铁汁们更新C语言之初识指针,指针是C语言中一个关键且强大的概念,理解和掌握指针对于编写高效、灵活的程序至关重要

    2024年02月13日
    浏览(25)
  • 第七章 C语言语句的机器级表示

    2023年04月12日
    浏览(38)
  • 自然语言处理: 第七章GPT的搭建

    在以transformer架构为框架的大模型遍地开花后,大模型的方向基本分成了三类分别是: decoder-only架构 , 其中以GPT系列为代表 encoder-only架构,其中以BERT系列为代表 encoder-decoder架构,标准的transformer架构以BART和T5为代表 大模型的使用方法如下: 分解成pre-train 和fine-tuning ,其中pr

    2024年02月13日
    浏览(33)
  • 【JavaWeb后端开发-第七章】SpingBoot原理

        在前面的所有章节当中,我们学习的都是web开发的技术使用,都是 面向应用层面 的,我们学会了怎么样去用。而我们今天所要学习的是 web后端开发 的最后一个篇章 springboot原理 篇,主要偏向于 底层原理 。 本章节的安排包括三个部分:     1. 配置优先级 :

    2024年01月20日
    浏览(35)
  • 第七章:敏捷开发工具方法-part1-敏捷开发基础

    敏捷开发背景 速度是企业竞争致胜的关键因素,软件项目的最大挑战在于一方面需要应付变动中的需求,一方面需要在有限的时间完成项目,传统的软件工程难以满足这些要求 所以软件团队除了在技术上必须日益精进,更需要运用有效的开发流程,以确保团队能够发挥综效

    2024年02月09日
    浏览(39)
  • Qt5开发及实例V2.0-第七章-Qt图形视图框架

    7.1.1 Graphics View的特点 Graphics View框架结构的主要特点如下。 (1)Graphics View框架结构中,系统可以利用Qt绘图系统的反锯齿、OpenGL工具来改善绘图性能。 (2)Graphics View支持事件传播体系结构,可以使图元在场景(scene)中的交互能力提高1倍,图元能够处理键盘事件和鼠标事

    2024年02月07日
    浏览(31)
  • 第七章:敏捷开发工具方法-part2-CI/CD工具介绍

    什么是CI/Cd? CI-Continuous integration : 持续集成 是指多名开发者在开发不同功能代码的过程当中,可以频繁的将代吗行合并到一起并切相互不影响工作。 CD-continuous deployment : 持续部署 是基于某种工具或平台实现代码自动化的构建、测试和部署到线上环境以实现交付高质量的

    2024年02月09日
    浏览(42)
  • 第七章 函数矩阵

    和矩阵函数不同的是,函数矩阵本质上是一个矩阵,是以函数作为元素的矩阵。 矩阵函数本质上是一个矩阵,是以矩阵作为自变量的函数。 函数矩阵和数字矩阵的运算法则完全相同。 不过矩阵的元素 a i j ( x ) a_{ij}(x) a ij ​ ( x ) 需要是闭区间 [ a , b ] [a,b] [ a , b ] 上的实函数

    2024年02月04日
    浏览(33)
  • 第七章金融中介

             金融中介是通过向资金盈余者发行 间接融资合约( 如存款单),并和资金短缺者达成 间接投资合约 (发放信贷)或购买其发行的证券,在资金供求方之间融通资金,对资金跨期、跨域进行优化配置的金融机构。         金融体系由金融市场和金融中介构成,以银行业为

    2024年02月04日
    浏览(35)
  • 第七章 测试

    7.1.1 选择程序设计语言 1. 计算机程序设计语言基本上可以分为汇编语言和高级语言 2. 从应用特点看,高级语言可分为基础语言、结构化语言、专用语言 01 有理想的模块化机制; 02 可读性好的控制结构和数据结构; 03 便于调试和提高软件可靠性; 04 编译程序发现程序错误的

    2024年02月08日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包