赞
踩
[写在前面: 文章多处用到gif动图,如未自动播放,请点击图片]
ArkTS 是 HarmonyOS 优选主力开发语言。ArkTS 是基于 TypeScript (TS) 扩展的一门语言,继承了 TS 的所有特性,是TS的超集。
主要是扩展了以下几个方面:
声明式UI描述和自定义组件:
多维度的状态管理机制:
ArkTS可以使用@State、@Prop 等装饰器以及 LocalStorage 等管理应用的状态。
主要表现在:
渲染控制的能力:
ArkTS提供了灵活的渲染控制功能,使开发者能够根据应用的状态动态渲染UI内容。
主要表现在:
概念 | 描述 |
---|---|
装饰器 | 用于装饰类、结构、方法以及变量,赋予其特殊含义。例如,@Component 表示自定义组件,@Entry 表示入口组件,@State 表示状态变量。状态变量的变化会触发UI刷新。 |
UI描述 | 使用声明式的方式描述UI的结构,通常体现在 build() 方法中的代码块。 |
自定义组件 | 可复用的UI单元,可组合其他组件。被 @Component 装饰的 struct xxx 就是一个自定义组件。 |
系统组件 | ArkUI框架中默认内置的基础和容器组件,可以直接调用。 常见系统组件:Column 、Text 、Divider 、Button 。 |
属性方法 | 组件可以通过链式调用配置多项属性的方法。例如,fontSize() 、width() 、height() 、backgroundColor() 可以用于设置组件的样式和尺寸等属性。 |
事件方法 | 组件可以通过链式调用设置多个事件的响应逻辑。例如,在 Button 后面的 onClick() 就是一个事件方法,用于处理按钮被点击时的逻辑。 |
经典案例代码示例:
// 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%') } }
效果如下:
ArkTS采用声明方式组合和扩展组件来描述应用程序的UI,同时提供了基本的属性、事件和子组件配置方法
1. 创建组件:
无参数创建:对于没有必选构造参数的组件,可以直接在组件后面使用空括号,例如 Divider()
。
有参数创建:如果组件包含构造参数,可以在括号内配置相应参数,例如 Text('你好')
。
Column() {
Text('item 1')
Divider()
Text('item 2')
}
2. 配置属性:
使用属性方法通过“.”链式调用配置系统组件的样式和其他属性,建议每个属性方法单独写一行,如设置字体大小、宽度、高度等。
Text('test')
.fontSize(12)
3. 配置事件:
通过事件方法以“.”链式调用配置系统组件支持的事件,可使用lambda表达式、匿名函数表达式或组件的成员函数来定义
Button('Click me')
.onClick(() => {
this.myText = 'ArkUI';
})
在ArkUI中,UI显示的内容由组件构成,其中框架直接提供的组件称为系统组件,而开发者定义的组件则被称为自定义组件。
自定义组件具有以下特点:
可组合: 允许开发者组合使用系统组件及其属性和方法。
可重用: 自定义组件可以被其他组件重用,作为不同实例在不同的父组件或容器中使用。
数据驱动UI更新: 通过状态变量的改变来驱动UI的刷新。
自定义组件的结构:
概念 | 描述 |
---|---|
struct | 基于 struct 实现自定义组件,结构为 struct + 自定义组件名 + {...} 。实例化时可以省略 new 。 |
@Component | 装饰器,仅能装饰使用 struct 关键字声明的数据结构。被装饰后的结构具备组件化能力,需要实现 build 方法描述UI。一个 struct 只能被一个 @Component 装饰。 |
build() 函数 | 用于定义自定义组件的声明式 UI 描述。自定义组件必须实现 build() 函数。 |
@Entry | 装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用一个 @Entry 装饰的自定义组件。可以接受一个可选的 LocalStorage 参数。 |
以下示例展示了自定义组件GreetingComponent
的基本用法:
@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()
函数中多次创建,实现自定义组件的重用。
@Entry
@Component
struct ParentComponent {
build() {
Column() {
Text('ArkUI Greetings');
GreetingComponent({ greeting: 'Hello, World!' });
Divider();
GreetingComponent({ greeting: '你好!' });
}
}
}
成员函数/变量
自定义组件除了必须要实现build()函数外,还可以实现其他成员函数,成员函数具有以下约束:
自定义组件可以包含成员变量,成员变量具有以下约束:
build()函数
@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 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方法的返回值
@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()) } } }
自定义组件通过“.”
链式调用的形式设置通用样式。
@Component struct MyComponent2 { build() { Button(`Hello World`) } } @Entry @Component struct MyComponent { build() { Row() { MyComponent2() // 为自定义组件添加通用样式 .width(200) .height(300) .backgroundColor(Color.Red) } } }
可以由一个或者多个自定义组件组成,@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 | 在自定义组件即将销毁时执行。 |
示例代码:
// 包含两个自定义组件,一个是被@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'; }) } }
定义的语法:
@Builder MyBuilderFunction({ ... })
使用方法:
this.MyBuilderFunction({ ... })
示例如下:
@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
// 尾随闭包初始化组件 @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'; }) } } } }
效果如下:
如果每个组件的样式都需要单独设置,在开发过程中会出现大量代码在进行重复样式设置,虽然可以复制粘贴,但为了代码简洁性和后续方便维护,可以使用公共样式装饰器@Styles。
使用方法:
// 反例: @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() } } } }
使用规则
// 和@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) } } } }
// 多态样式 // 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%') } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。