未验证 提交 c43bb241 编写于 作者: O openharmony_ci 提交者: Gitee

!11013 ArkTS语言挑单到3.2beta3

Merge pull request !11013 from 田雨/OpenHarmony-3.2-Beta3
...@@ -4,9 +4,9 @@ ArkTS通过装饰器@Component和@Entry装饰struct关键字声明的数据结 ...@@ -4,9 +4,9 @@ ArkTS通过装饰器@Component和@Entry装饰struct关键字声明的数据结
## 基本概念 ## 基本概念
- struct:组件可以基于struct实现,不能有继承关系,对于struct的实例化,可以省略new。 - struct:自定义组件可以基于struct实现,不能有继承关系,对于struct的实例化,可以省略new。
- 装饰器:装饰器给被装饰的对象赋予某一种能力,其不仅可以装饰类或结构体,还可以装饰类的属性。多个装饰器可以叠加到目标元素,定义在同一行上或者多行上,推荐定义在多行上 - 装饰器:装饰器给被装饰的对象赋予某一种能力,其不仅可以装饰类或结构体,还可以装饰类的属性。多个装饰器可以叠加到目标元素上,定义在同一行中或者分开多行,推荐分开多行定义
```ts ```ts
@Entry @Entry
...@@ -15,7 +15,7 @@ ArkTS通过装饰器@Component和@Entry装饰struct关键字声明的数据结 ...@@ -15,7 +15,7 @@ ArkTS通过装饰器@Component和@Entry装饰struct关键字声明的数据结
} }
``` ```
- build函数:自定义组件必须定义build函数,并且自定义组件禁止自定义构造函数。build函数满足Builder构造器接口定义,用于定义组件的声明式UI描述。 - build函数:自定义组件必须定义build函数,并且禁止自定义构造函数。build函数满足Builder构造器接口定义,用于定义组件的声明式UI描述。
```ts ```ts
interface Builder { interface Builder {
...@@ -23,21 +23,21 @@ ArkTS通过装饰器@Component和@Entry装饰struct关键字声明的数据结 ...@@ -23,21 +23,21 @@ ArkTS通过装饰器@Component和@Entry装饰struct关键字声明的数据结
} }
``` ```
- @Component:装饰struct,结构体在装饰后具有基于组件的能力,需要实现build方法来更新UI。 - @Component:装饰struct,结构体在装饰后具有基于组件的能力,需要实现build方法来创建UI。
- @Entry: 装饰struct,组件被装饰后作为页面的入口,页面加载时将被渲染显示。 - @Entry: 装饰struct,组件被装饰后作为页面的入口,页面加载时将被渲染显示。
- @Preview:装饰struct, 用@Preview装饰的自定义组件可以在DevEco Studio的预览器上进行实时预览,加载页面时,将创建并呈现@Preview装饰的自定义组件。 - @Preview:装饰struct, 用@Preview装饰的自定义组件可以在DevEco Studio的预览器上进行实时预览,加载页面时,将创建并显示@Preview装饰的自定义组件。
> **说明:** 在单个源文件中,最多可以使用10个@Preview装饰自定义组件,更多说明请参考[查看ArkTS组件预览效果](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-previewing-app-service-0000001218760596#section146052489820)。 > **说明:** 在单个源文件中,最多可以使用10个@Preview装饰自定义组件,更多说明请参考[查看ArkTS组件预览效果](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-previewing-app-service-0000001218760596#section146052489820)。
- 链式调用:以 "." 链式调用的方式配置UI结构及其属性、事件等。 - 链式调用:以 "." 链式调用的方式配置UI组件的属性方法、事件方法等。
## UI描述规范 ## UI描述规范
### 无参数构造配置 ### 无参数构造配置
组件的接口定义不包含必选构造参数,组件后面的“()”中不需要配置任何内容。例如,Divider组件不包含构造参数: 如果组件的接口定义中不包含必选构造参数,组件后面的“()”中不需要配置任何内容。例如,Divider组件不包含构造参数:
```ts ```ts
Column() { Column() {
...@@ -49,27 +49,27 @@ Column() { ...@@ -49,27 +49,27 @@ Column() {
### 必选参数构造配置 ### 必选参数构造配置
如果组件的接口定义中包含必选构造参数,则在组件后面的“()”中必须配置参数,参数可以使用常量进行赋值。 如果组件的接口定义中包含必选构造参数,则在组件后面的“()”中必须配置相应参数,参数可以使用常量进行赋值。
例如: 例如:
- Image组件的必选参数src: - Image组件的必选参数src:
```ts ```ts
Image('http://xyz/a.jpg') Image('https://xyz/test.jpg')
``` ```
- Text组件的必选参数content: - Text组件的必选参数content:
```ts ```ts
Text('123') Text('test')
``` ```
变量或表达式也可以用于参数赋值,其中表达式返回的结果类型必须满足参数类型要求。例如,传递变量或表达式来构造Image和Text组件的参数: 变量或表达式也可以用于参数赋值,其中表达式返回的结果类型必须满足参数类型要求,变量的定义详见[页面级变量的状态管理](arkts-state-mgmt-page-level.md)[应用级变量的状态管理](arkts-state-mgmt-application-level.md)。例如,设置变量或表达式来构造Image和Text组件的参数:
```ts ```ts
Image(this.imagePath) Image(this.imagePath)
Image('http://' + this.imageUrl) Image('https://' + this.imageUrl)
Text(`count: ${this.count}`) Text(`count: ${this.count}`)
``` ```
...@@ -80,14 +80,14 @@ Text(`count: ${this.count}`) ...@@ -80,14 +80,14 @@ Text(`count: ${this.count}`)
- 配置Text组件的字体大小属性: - 配置Text组件的字体大小属性:
```ts ```ts
Text('123') Text('test')
.fontSize(12) .fontSize(12)
``` ```
- 使用"."操作进行链式调用并同时配置组件的多个属性,如下所示: - 使用"."运算符进行链式调用并同时配置组件的多个属性,如下所示:
```ts ```ts
Image('a.jpg') Image('test.jpg')
.alt('error.jpg') .alt('error.jpg')
.width(100) .width(100)
.height(100) .height(100)
...@@ -98,12 +98,12 @@ Text(`count: ${this.count}`) ...@@ -98,12 +98,12 @@ Text(`count: ${this.count}`)
```ts ```ts
Text('hello') Text('hello')
.fontSize(this.size) .fontSize(this.size)
Image('a.jpg') Image('test.jpg')
.width(this.count % 2 === 0 ? 100 : 200) .width(this.count % 2 === 0 ? 100 : 200)
.height(this.offset + 100) .height(this.offset + 100)
``` ```
- 对于系统内置组件,框架还为其属性预定义了一些[枚举类型](../reference/arkui-ts/ts-appendix-enums.md)供开发人员调用,枚举类型必须满足参数类型要求,枚举值可以作为参数传递。可以按以下方式配置Text组件的颜色和字体属性: - 对于系统内置组件,框架还为其属性预定义了一些[枚举类型](../reference/arkui-ts/ts-appendix-enums.md)供开发人员调用,枚举类型可以作为参数传递,且必须满足参数类型要求。例如,可以按以下方式配置Text组件的颜色和字体属性:
```ts ```ts
Text('hello') Text('hello')
...@@ -114,7 +114,7 @@ Text(`count: ${this.count}`) ...@@ -114,7 +114,7 @@ Text(`count: ${this.count}`)
### 事件配置 ### 事件配置
通过事件方法可以配置组件支持的事件。 通过事件方法可以配置组件支持的事件,事件方法紧随组件,并用"."运算符连接
- 使用lambda表达式配置组件的事件方法: - 使用lambda表达式配置组件的事件方法:
...@@ -138,18 +138,18 @@ Text(`count: ${this.count}`) ...@@ -138,18 +138,18 @@ Text(`count: ${this.count}`)
```ts ```ts
myClickHandler(): void { myClickHandler(): void {
this.counter += 2
} }
... ...
Button('add counter') Button('add counter')
.onClick(this.myClickHandler) .onClick(this.myClickHandler)
``` ```
### 子组件配置 ### 子组件配置
对于支持子组件配置的组件,例如容器组件,在"{ ... }"里为组件添加子组件的UI描述。Column、Row、Stack、Button、Grid和List组件都是容器组件。 对于支持子组件配置的组件,例如容器组件,在"{ ... }"里为组件添加子组件的UI描述。Column、Row、Stack、Grid、List等组件都是容器组件。
- 以下是简单的Column示例: - 以下是简单的Column示例:
...@@ -164,32 +164,40 @@ Text(`count: ${this.count}`) ...@@ -164,32 +164,40 @@ Text(`count: ${this.count}`)
} }
``` ```
- 可以嵌套多个子组件 - 容器组件之间也可以互相嵌套,实现相对复杂的多级嵌套效果
```ts ```ts
Column() { Column() {
Column() { Row() {
Button() { Image('test1.jpg')
Text('+ 1') .width(100)
}.type(ButtonType.Capsule) .height(100)
.onClick(() => console.log ('+1 clicked!')) Button('click +1')
Image('1.jpg') .onClick(() => {
} console.info('+1 clicked!')
Divider() })
Column() { }
Button() {
Text('+ 2') Divider()
}.type(ButtonType.Capsule) Row() {
.onClick(() => console.log ('+2 clicked!')) Image('test2.jpg')
Image('2.jpg') .width(100)
} .height(100)
Divider() Button('click +2')
Column() { .onClick(() => {
Button() { console.info('+2 clicked!')
Text('+ 3') })
}.type(ButtonType.Capsule) }
.onClick(() => console.log('+3 clicked!'))
Image('3.jpg') Divider()
} Row() {
}.alignItems(HorizontalAlign.Center) Image('test3.jpg')
.width(100)
.height(100)
Button('click +3')
.onClick(() => {
console.info('+3 clicked!')
})
}
}
``` ```
\ No newline at end of file
# 动态构建UI元素 # 动态构建UI元素
前面章节介绍的是如何创建一个组件内部UI结构固定的自定义组件,为了满足开发者自定义组件内部UI结构的需求,ArkTS同时提供了动态构建UI元素的能力。 [基本UI描述](arkts-basic-ui-description.md)介绍的是如何创建一个内部UI结构固定的自定义组件,为了满足开发者自定义组件内部UI结构的需求,ArkTS同时提供了动态构建UI元素的能力。
## @Builder ## @Builder
可通过@Builder装饰器进行描述,该装饰器可以修饰一个函数,此函数可以在build函数之外声明,并在build函数中或其他@Builder修饰的函数中使用,从而实现在一个自定义组件内快速生成多个布局内容。使用方式如下面示例所示。
可通过@Builder装饰器进行描述,该装饰器可以修饰一个函数,此函数可以在build函数之外声明,并在build函数中或其他@Builder修饰的函数中使用,在一个自定义组件内快速生成多个布局内容。使用方式如下面示例所示。
```ts ```ts
// xxx.ets // xxx.ets
@Component @Component
struct CompB { struct CompB {
@State CompValue: string = ''; @State CompValue: string = ''
aboutToAppear() { aboutToAppear() {
console.info('CompB aboutToAppear.'); console.info('CompB aboutToAppear.')
} }
aboutToDisappear() { aboutToDisappear() {
console.info('CompB aboutToDisappear.'); console.info('CompB aboutToDisappear.')
} }
build() { build() {
Column() { Column() {
Button(this.CompValue); Button(this.CompValue)
.margin(5)
} }
} }
} }
...@@ -33,22 +31,24 @@ struct CompB { ...@@ -33,22 +31,24 @@ struct CompB {
@Entry @Entry
@Component @Component
struct CompA { struct CompA {
size1: number = 100; size1: number = 100
@State CompValue1: string = "Hello,CompValue1"; @State CompValue1: string = "Hello,CompValue1"
@State CompValue2: string = "Hello,CompValue2"; @State CompValue2: string = "Hello,CompValue2"
@State CompValue3: string = "Hello,CompValue3"; @State CompValue3: string = "Hello,CompValue3"
// @Builder装饰的函数内使用自定义组件 // @Builder装饰的函数CompC内使用自定义组件CompB
@Builder CompC(value: string) { @Builder CompC(value: string) {
CompB({ CompValue: value }); CompB({ CompValue: value })
} }
@Builder SquareText(label: string) { @Builder SquareText(label: string) {
Text(label) Text(label)
.fontSize(18)
.width(1 * this.size1) .width(1 * this.size1)
.height(1 * this.size1) .height(1 * this.size1)
} }
// @Builder装饰的函数RowOfSquareTexts内使用@Builder装饰的函数SquareText
@Builder RowOfSquareTexts(label1: string, label2: string) { @Builder RowOfSquareTexts(label1: string, label2: string) {
Row() { Row() {
this.SquareText(label1) this.SquareText(label1)
...@@ -63,7 +63,6 @@ struct CompA { ...@@ -63,7 +63,6 @@ struct CompA {
Row() { Row() {
this.SquareText("A") this.SquareText("A")
this.SquareText("B") this.SquareText("B")
// or as long as tsc is used
} }
.width(2 * this.size1) .width(2 * this.size1)
.height(1 * this.size1) .height(1 * this.size1)
...@@ -71,9 +70,9 @@ struct CompA { ...@@ -71,9 +70,9 @@ struct CompA {
this.RowOfSquareTexts("C", "D") this.RowOfSquareTexts("C", "D")
Column() { Column() {
// 使用三次@Builder装饰的自定义组件 // 使用三次@Builder装饰的自定义组件
this.CompC(this.CompValue1); this.CompC(this.CompValue1)
this.CompC(this.CompValue2); this.CompC(this.CompValue2)
this.CompC(this.CompValue3); this.CompC(this.CompValue3)
} }
.width(2 * this.size1) .width(2 * this.size1)
.height(2 * this.size1) .height(2 * this.size1)
...@@ -83,34 +82,37 @@ struct CompA { ...@@ -83,34 +82,37 @@ struct CompA {
} }
} }
``` ```
![builder](figures/builder.PNG)
## @BuilderParam<sup>8+<sup> ## @BuilderParam<sup>8+<sup>
@BuilderParam装饰器用于修饰自定义组件内函数类型的属性(例如:`@BuilderParam content: () => any;`),并且在初始化自定义组件时被@BuilderParam修饰的属性必须赋值。 @BuilderParam装饰器用于修饰自定义组件内函数类型的属性(例如:`@BuilderParam noParam: () => void`),并且在初始化自定义组件时被@BuilderParam修饰的属性必须赋值。
### 引入动机 ### 引入动机
当开发者创建自定义组件,想对该组件添加特定功能时(如:仅对自定义组件添加一个点击跳转操作)。若直接在组件内嵌入事件方法,将会导致所有初始化该组件的地方均增加了该功能。为解决此问题,引入了@BuilderParam装饰器,此装饰器修饰的属性值可为@Builder修饰的方法,开发者可在初始化自定义组件时对此属性进行赋值,为自定义组件增加特定的的功能。 当开发者创建自定义组件,并想对该组件添加特定功能时(例如在自定义组件中添加一个点击跳转操作)。若直接在组件内嵌入事件方法,将会导致所有引入该自定义组件的地方均增加了该功能。为解决此问题,引入了@BuilderParam装饰器,此装饰器修饰的属性值可为@Builder装饰的函数,开发者可在初始化自定义组件时对此属性进行赋值,为自定义组件增加特定的功能。
### 参数初始化组件 ### 参数初始化组件
通过参数初始化组件时,将@Builder装饰的方法赋值给@BuilderParam修饰的属性,并在自定义组件内调用content属性值。对@BuilderParam修饰的属性进行赋值时不带参数(如:`content: this.specificParam`),则此属性的类型需定义成无返回值的函数(如:`@BuilderParam content: () => void`)。若带参数(如:`callContent: this.specificParam1("111")`),则此属性的类型需定义成any(如:`@BuilderParam callContent: any;`)。 通过参数初始化组件时,将@Builder装饰的函数赋值给@BuilderParam修饰的属性,并在自定义组件内调用该属性值。若@BuilderParam修饰的属性在进行赋值时不带参数(如:`noParam: this.specificNoParam`),则此属性的类型需定义为无返回值的函数(如:`@BuilderParam noParam: () => void`);若带参数(如:`withParam: this.SpecificWithParam('WithParamA')`),则此属性的类型需定义成any(如:`@BuilderParam withParam: any`)。
```ts ```ts
// xxx.ets // xxx.ets
@Component @Component
struct CustomContainer { struct CustomContainer {
header: string = ""; header: string = ''
@BuilderParam noParam: () => void; @BuilderParam noParam: () => void
@BuilderParam withParam: any; @BuilderParam withParam: any
footer: string = ""; footer: string = ''
build() { build() {
Column() { Column() {
Text(this.header) Text(this.header)
.fontSize(50) .fontSize(30)
this.noParam() this.noParam()
this.withParam() this.withParam()
Text(this.footer) Text(this.footer)
.fontSize(50) .fontSize(30)
} }
} }
} }
...@@ -120,69 +122,86 @@ struct CustomContainer { ...@@ -120,69 +122,86 @@ struct CustomContainer {
struct CustomContainerUser { struct CustomContainerUser {
@Builder specificNoParam() { @Builder specificNoParam() {
Column() { Column() {
Text("noParam").fontSize(50) Text('noParam').fontSize(30)
} }
} }
@Builder SpecificWithParam(label: string) { @Builder SpecificWithParam(label: string) {
Column() { Column() {
Text(label).fontSize(50) Text(label).fontSize(30)
} }
} }
build() { build() {
Column() { Column() {
CustomContainer({ CustomContainer({
header: "Header", header: 'HeaderA',
noParam: this.specificNoParam,
withParam: this.SpecificWithParam('WithParamA'),
footer: 'FooterA'
})
Divider()
.strokeWidth(3)
.margin(10)
CustomContainer({
header: 'HeaderB',
noParam: this.specificNoParam, noParam: this.specificNoParam,
withParam: this.SpecificWithParam("WithParam"), withParam: this.SpecificWithParam('WithParamB'),
footer: "Footer", footer: 'FooterB'
}) })
} }
} }
} }
``` ```
![builder1](figures/builder1.PNG)
### 尾随闭包初始化组件 ### 尾随闭包初始化组件
在自定义组件中使用@BuilderParam修饰的属性接收尾随闭包(在初始化自定义组件时,组件名称紧跟一个大括号“{}”形成尾随闭包场景(`CustomComponent(){}`)。开发者可把尾随闭包看做一个容器,向其填充内容,如在闭包内增加组件(`{Column(){Text("content")}`),闭包内语法规范与build函数一致。此场景下自定义组件内有且仅有一个使用@BuilderParam修饰的属性。 在自定义组件中使用@BuilderParam修饰的属性时也可通过尾随闭包进行初始化(在初始化自定义组件时,组件后紧跟一个大括号“{}”形成尾随闭包场景(`CustomContainer(){}`)。开发者可把尾随闭包看做一个容器,向其中填充内容,如在闭包内增加组件(`{Column(){...}`),闭包内语法规范与build函数一致。此场景下自定义组件内有且仅有一个使用@BuilderParam修饰的属性。
示例:在闭包内增加Column组件并添加点击事件,在新增的Column组件内调用@Builder修饰的specificParam方法,点击Column组件后该改变自定义组件中header的属性值为“changeHeader”。并且在初始化自定义组件时会把尾随闭包的内容赋值给使用@BuilderParam修饰的closer属性。 示例:在闭包内添加Column组件并设置点击事件,在Column组件内调用@Builder修饰的specificParam函数,点击Column组件后将自定义组件CustomContainer中header的属性值由“header”改变为“changeHeader”。在初始化自定义组件CustomContainer时,尾随闭包的内容会被赋值给@BuilderParam修饰的closer属性。
```ts ```ts
// xxx.ets // xxx.ets
@Component @Component
struct CustomContainer { struct CustomContainer {
header: string = ""; header: string = ''
@BuilderParam closer: () => void; @BuilderParam closer: () => void
build() { build() {
Column() { Column() {
Text(this.header) Text(this.header)
.fontSize(50) .fontSize(30)
this.closer() this.closer()
} }
} }
} }
@Builder function specificParam(label1: string, label2: string) { @Builder function specificParam(label1: string, label2: string) {
Column() { Column() {
Text(label1) Text(label1)
.fontSize(50) .fontSize(30)
Text(label2) Text(label2)
.fontSize(50) .fontSize(30)
} }
} }
@Entry @Entry
@Component @Component
struct CustomContainerUser { struct CustomContainerUser {
@State text: string = "header" @State text: string = 'header'
build() { build() {
Column() { Column() {
CustomContainer({ CustomContainer({
header: this.text, header: this.text,
}){ }) {
Column(){ Column() {
specificParam("111", "22") specificParam('testA', 'testB')
}.onClick(()=>{ }.backgroundColor(Color.Yellow)
this.text = "changeHeader" .onClick(() => {
this.text = 'changeHeader'
}) })
} }
} }
...@@ -190,87 +209,103 @@ struct CustomContainerUser { ...@@ -190,87 +209,103 @@ struct CustomContainerUser {
} }
``` ```
![builder2](figures/builder2.gif)
## @Styles ## @Styles
ArkTS为了避免开发者对重复样式的设置,通过@Styles装饰器可以将多条样式设置提炼成一个方法,直接在组件声明的位置使用。@Styles装饰器将新的属性函数添加到基本组件上,如Text、Column、Button等。当前@Styles仅支持通用属性。通过@Styles装饰器可以快速定义并复用组件的自定义样式 ArkTS为了避免开发者对重复样式的设置,通过@Styles装饰器可以将多个样式设置提炼成一个方法,直接在组件声明时调用,通过@Styles装饰器可以快速定义并复用自定义样式。当前@Styles仅支持通用属性
@Styles可以定义在组件内或组件外,在组件外定义时需在方法前添加function关键字,组件内定义时不需要添加function关键字。 @Styles可以定义在组件内或组件外,在组件外定义时需在方法名前面添加function关键字,组件内定义时则不需要添加function关键字。
```ts ```ts
// xxx.ets // xxx.ets
@Styles function globalFancy() { @Styles function globalFancy () {
.backgroundColor(Color.Red) .width(150)
.height(100)
.backgroundColor(Color.Pink)
} }
@Entry @Entry
@Component @Component
struct FancyUse { struct FancyUse {
@Styles componentFancy() { @Styles componentFancy() {
.backgroundColor(Color.Blue) .width(100)
.height(200)
.backgroundColor(Color.Yellow)
} }
build() { build() {
Column({ space: 10 }) { Column({ space: 10 }) {
Text("Fancy") Text('FancyA')
.globalFancy() .globalFancy()
.width(100)
.height(100)
.fontSize(30) .fontSize(30)
Text("Fancy") Text('FancyB')
.globalFancy()
.fontSize(20)
Text('FancyC')
.componentFancy() .componentFancy()
.width(100)
.height(100)
.fontSize(30) .fontSize(30)
Text('FancyD')
.componentFancy()
.fontSize(20)
} }
} }
} }
``` ```
![styles](figures/styles.PNG)
@Styles还可以在[StateStyles](../reference/arkui-ts/ts-universal-attributes-polymorphic-style.md)属性内部使用,在组件处于不同的状态时赋予相应的属性。 @Styles还可以在[StateStyles](../reference/arkui-ts/ts-universal-attributes-polymorphic-style.md)属性内部使用,在组件处于不同的状态时赋予相应的属性。
在StateStyles内可以直接调用组件外定义的Styles,但需要通过this关键字调用组件内定义的Styles 在StateStyles内可以直接调用组件外定义的@Styles方法,但需要通过this关键字调用组件内定义的@Styles方法
```ts ```ts
// xxx.ets // xxx.ets
@Styles function globalFancy() { @Styles function globalFancy () {
.width(100) .width(120)
.height(100) .height(120)
.backgroundColor(Color.Green)
} }
@Entry @Entry
@Component @Component
struct FancyUse { struct FancyUse {
@Styles componentFancy() { @Styles componentFancy() {
.width(50) .width(80)
.height(50) .height(80)
.backgroundColor(Color.Red)
} }
build() { build() {
Row({ space: 10 }) { Row({ space: 10 }) {
Button() { Button('Fancy')
Text("Fancy") .stateStyles({
} normal: {
.stateStyles({ .width(100)
normal: { .height(100)
.width(80) .backgroundColor(Color.Blue)
.height(80) },
}, disabled: this.componentFancy,
disabled: this.componentFancy, pressed: globalFancy
pressed: globalFancy })
})
} }
} }
} }
``` ```
![styles1](figures/styles1.gif)
## @Extend ## @Extend
@Extend装饰器将新的属性函数添加到内置组件上,如Text、Column、Button等。通过@Extend装饰器可以快速的扩展原生组件。@Extend装饰器不能用在自定义组件struct定义框内。 @Extend装饰器将新的属性方法添加到Text、Column、Button等内置组件上,通过@Extend装饰器可以快速地扩展原生组件。@Extend不能定义在自定义组件struct内。
```ts ```ts
// xxx.ets // xxx.ets
@Extend(Text) function fancy(fontSize: number) { @Extend(Text) function fancy (fontSize: number) {
.fontColor(Color.Red) .fontColor(Color.Red)
.fontSize(fontSize) .fontSize(fontSize)
.fontStyle(FontStyle.Italic) .fontStyle(FontStyle.Italic)
.fontWeight(600)
} }
@Entry @Entry
...@@ -282,63 +317,69 @@ struct FancyUse { ...@@ -282,63 +317,69 @@ struct FancyUse {
.fancy(16) .fancy(16)
Text("Fancy") Text("Fancy")
.fancy(24) .fancy(24)
Text("Fancy")
.fancy(32)
} }
} }
} }
``` ```
> **说明:** > **说明:**
> >
> - @Extend装饰器不能用在自定义组件struct定义框内。 > - @Extend装饰器不能定义在自定义组件struct内。
> - @Extend装饰器内仅支持属性函数语句。 > - @Extend装饰器内仅支持属性方法设置。
![extend](figures/extend.PNG)
## @CustomDialog ## @CustomDialog
@CustomDialog装饰器用于装饰自定义弹窗,使得弹窗可以动态设置样式。 @CustomDialog装饰器用于装饰自定义弹窗组件,使得弹窗可以动态设置内容及样式。
```ts ```ts
// custom-dialog-demo.ets // xxx.ets
@CustomDialog @CustomDialog
struct DialogExample { struct DialogExample {
controller: CustomDialogController; controller: CustomDialogController
action: () => void; action: () => void
build() { build() {
Row() { Row() {
Button ("Close CustomDialog") Button('Close CustomDialog')
.onClick(() => { .onClick(() => {
this.controller.close(); this.controller.close()
this.action(); this.action()
}) })
}.padding(20) }.padding(20)
} }
} }
@Entry @Entry
@Component @Component
struct CustomDialogUser { struct CustomDialogUser {
dialogController : CustomDialogController = new CustomDialogController({ dialogController: CustomDialogController = new CustomDialogController({
builder: DialogExample({action: this.onAccept}), builder: DialogExample({ action: this.onAccept }),
cancel: this.existApp, cancel: this.existApp,
autoCancel: true autoCancel: true
}); });
onAccept() { onAccept() {
console.log("onAccept"); console.info('onAccept');
} }
existApp() {
console.log("Cancel dialog!");
}
build() { existApp() {
Column() { console.info('Cancel dialog!');
Button("Click to open Dialog") }
.onClick(() => {
this.dialogController.open() build() {
}) Column() {
} Button('Click to open Dialog')
.onClick(() => {
this.dialogController.open()
})
} }
}
} }
``` ```
![custom-dialog-demo](figures/custom-dialog-demo.gif) ![customdialog](figures/customDialog.gif)
\ No newline at end of file \ No newline at end of file
...@@ -4,27 +4,27 @@ ArkTS是OpenHarmony优选的主力应用开发语言。ArkTS基于TypeScript( ...@@ -4,27 +4,27 @@ ArkTS是OpenHarmony优选的主力应用开发语言。ArkTS基于TypeScript(
- ArkTS继承了TS的所有特性。 - ArkTS继承了TS的所有特性。
- 当前,ArkTS在TS基础上主要扩展了[声明式UI](arkts-basic-ui-description.md)能力,让开发者以更简洁、更自然的方式开发高性能应用。 - 当前,ArkTS在TS的基础上主要扩展了[声明式UI](arkts-basic-ui-description.md)能力,让开发者能够以更简洁、更自然的方式开发高性能应用。
当前扩展的声明式UI包括如下特性。 当前扩展的声明式UI能力包括如下特性。
- [基本UI描述](arkts-basic-ui-description.md):ArkTS定义了各种装饰器、自定义组件、UI描述机制,再配合UI开发框架中的UI内置组件、事件方法、属性方法等共同构成了UI开发的主体。 - [基本UI描述](arkts-basic-ui-description.md):ArkTS定义了各种装饰器、自定义组件、UI描述机制,再配合UI开发框架中的内置组件及其相关的事件方法、属性方法等共同构成了UI开发的主体。
- [状态管理](arkts-state-mgmt-page-level.md):ArkTS提供了多维度的状态管理机制,在UI开发框架中,和UI相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,也可以是全局范围内的传递,还可以是 跨设备传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活的利用这些能力来实现数据和UI的联动。 - [状态管理](arkts-state-mgmt-page-level.md):ArkTS提供了多维度的状态管理机制,在UI开发框架中,和UI相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,也可以是应用全局范围内的传递,还可以是跨设备传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活的利用这些能力来实现数据和UI的联动。
- [动态构建UI元素](arkts-dynamic-ui-elememt-building.md):ArkTS提供了动态构建UI元素的能力,不仅可以自定义组件内部的UI结构,还可复用组件样式,扩展原生组件。 - [动态构建UI元素](arkts-dynamic-ui-elememt-building.md):ArkTS提供了动态构建UI元素的能力,不仅可以自定义组件内部的UI结构,还可复用组件样式,扩展原生组件。
- [渲染控制](arkts-rendering-control.md):ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的部分内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。 - [渲染控制](arkts-rendering-control.md):ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。
- [使用限制与扩展](arkts-restrictions-and-extensions.md):ArkTS在使用过程中存在限制与约束,同时也扩展了双向绑定等能力。 - [使用限制与扩展](arkts-restrictions-and-extensions.md):ArkTS在使用过程中存在限制与约束,同时也扩展了双向绑定等能力。
- 未来,ArkTS会结合应用开发/运行的需求持续演进,逐步提供并行和并发能力增强、类型系统增强、分布式开发范式等更多特性。 - 未来,ArkTS会结合应用开发/运行的需求持续演进,逐步提供并行和并发能力增强、系统类型增强、分布式开发范式等更多特性。
下面我们以一个具体的示例来说明ArkTS的基本组成。如下图所示的代码示例,UI界面会两段文本和一个按钮,当开发者点击按钮时,文本内容会从'Hello World'变为 'Hello ArkUI'。 下面我们以一个具体的示例来说明ArkTS的基本组成。如下图所示的代码示例,UI界面包含两段文本、一条分割线和一个按钮,当开发者点击按钮时,文本内容会从'Hello World'变为 'Hello ArkUI'。
![arkts-get-started](figures/arkts-get-started.png) ![arkts-get-started](figures/arkts-get-started.png)
这个示例中所包含的ArkTS声明式开发范式的基本组成说明如下: 这个示例中所包含的ArkTS声明式开发范式的基本组成说明如下:
- 装饰器: 用于装饰类、结构、方法以及变量,赋予其特殊的含义,如上述示例中@Entry、@Component和@State都是装饰器。 具体而言,@Component表示这是个自定义组件;@Entry则表示这是个入口组件;@State表示组件中的状态变量,这个状态变换会引起UI变更 - 装饰器: 用于装饰类、结构、方法以及变量,赋予其特殊的含义,如上述示例中@Entry、@Component和@State都是装饰器。 具体而言,@Component表示这是个自定义组件;@Entry则表示这是个入口组件;@State表示这是组件中的状态变量,这个变量变化会触发UI刷新
- 自定义组件:可复用的UI单元,可组合其他组件,如上述被@Component装饰的struct Hello。 - 自定义组件:可复用的UI单元,可组合其他组件,如上述被@Component装饰的struct Hello。
- UI描述:声明式的方法来描述UI的结构,例如build()方法中的代码块。 - UI描述:声明式的方法来描述UI的结构,例如build()方法中的代码块。
- 内置组件:ArkTS中默认内置的基本组件和布局组件,开发者可以直接调用,如Column、Text、Divider、Button等。 - 内置组件:ArkTS中默认内置的基础组件、容器组件、媒体组件、绘制组件、画布组件等各种组件,开发者可以直接调用,如示例中的Column、Text、Divider、Button等。
- 属性方法:用于组件属性的配置,如fontSize()、width()、height()、color()等,可通过链式调用的方式设置多项属性。 - 属性方法:用于组件属性的配置,如fontSize()、width()、height()、color()等,可通过链式调用的方式设置多项属性。
- 事件方法:用于添加组件对事件的响应逻辑,统一通过事件方法进行设置,如跟随在Button后面的onClick() - 事件方法:用于添加组件对事件的响应逻辑,如跟随在Button后面的onClick(),同样可以通过链式调用的方式设置多个事件响应逻辑
# 渲染控制 # 渲染控制
ArkTS也提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的部分内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。 ArkTS也提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。
## 条件渲染 ## 条件渲染
...@@ -9,61 +9,61 @@ ArkTS也提供了渲染控制的能力。条件渲染可根据应用的不同状 ...@@ -9,61 +9,61 @@ ArkTS也提供了渲染控制的能力。条件渲染可根据应用的不同状
> **说明:** > **说明:**
> >
> - if条件语句可以使用状态变量。 > - if/else条件语句可以使用状态变量。
> >
> - 使用if可以使子组件的渲染依赖条件语句。 > - 使用if/else可以使子组件的渲染依赖条件语句。
> >
> - 必须在容器组件内使用。 > - 必须在容器组件内使用。
> >
> - 某些容器组件限制子组件的类型或数量。将if放置在这些组件内时,这些限制将应用于if和else语句内创建的组件。例如,Grid组件的子组件仅支持GridItem组件,在Grid组件内使用if时,则if条件语句内仅允许使用GridItem组件。 > - 某些容器组件限制子组件的类型或数量,将if/else用于这些组件内时,这些限制将同样应用于if/else语句内创建的组件。例如,Grid容器组件的子组件仅支持GridItem组件,在Grid内使用if/else时,则if/else语句内也仅允许使用GridItem组件。
```ts ```ts
Column() { Column() {
if (this.count < 0) { if (this.count < 0) {
Text('count is negative') Text('count is negative').fontSize(14)
} else if (this.count % 2 === 0) { } else if (this.count % 2 === 0) {
Divider() Text('count is even').fontSize(14)
Text('even') } else {
} else { Text('count is odd').fontSize(14)
Divider() }
Text('odd')
}
} }
``` ```
## 循环渲染 ## 循环渲染
通过循环渲染(ForEach组件)来迭代数组,并为每个数组项创建相应的组件,可减少代码复杂度。 通过循环渲染(ForEach)从数组中获取数据,并为每个数据项创建相应的组件,可减少代码复杂度。
``` ```
ForEach( ForEach(
arr: any[], arr: any[],
itemGenerator: (item: any, index?: number) => void, itemGenerator: (item: any, index?: number) => void,
keyGenerator?: (item: any, index?: number) => string keyGenerator?: (item: any, index?: number) => string
) )
``` ```
**参数:** **参数:**
| 参数名 | 参数类型 | 必填 | 参数描述 | | 参数名 | 参数类型 | 必填 | 参数描述 |
| ------------- | ------------------------------------- | ---- | ------------------------------------------------------------ | | ------------- | ------------------------------------- | ---- | ------------------------------------------------------------ |
| arr | any[] | 是 | 必须是数组,允许空数组,空数组场景下不会创建子组件。同时允许设置返回值为数组类型的函数,例如arr.slice(1, 3),设置的函数不得改变包括数组本身在内的任何状态变量,如Array.splice、Array.sort或Array.reverse这些改变原数组的函数。 | | arr | any[] | 是 | 必须是数组,允许设置为空数组,空数组场景下将不会创建子组件。同时允许设置返回值为数组类型的函数,例如arr.slice(1, 3),设置的函数不得改变包括数组本身在内的任何状态变量,如Array.splice、Array.sort或Array.reverse这些改变原数组的函数。 |
| itemGenerator | (item: any, index?: number) => void | 是 | 生成子组件的lambda函数,为给定数组项生成一个或多个子组件,单个组件和子组件列表必须括在大括号“{....}”中。 | | itemGenerator | (item: any, index?: number) => void | 是 | 生成子组件的lambda函数,为数组中的每一个数据项创建一个或多个子组件,单个子组件或子组件列表必须包括在大括号“{...}”中。 |
| keyGenerator | (item: any, index?: number) => string | 否 | 匿名参数,用于给定数组项生成唯一且稳定的键值。当子项在数组中的位置更改时,子项的键值不得更改,当数组中的子项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能够更好地识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则ForEach中的所有节点都将重建。 | | keyGenerator | (item: any, index?: number) => string | 否 | 匿名函数,用于给数组中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能够更好地识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则ForEach中的所有节点都将重建。 |
> **说明:** > **说明:**
> >
> - ForEach组件必须在容器组件内使用; > - ForEach必须在容器组件内使用。
> >
> - 生成的子组件允许在ForEach的父容器组件中,允许子组件生成器函数中包含if/else条件渲染,同时也允许ForEach包含在if/else条件渲染语句中; > - 生成的子组件应当是允许包含在ForEach父容器组件中的子组件。
> >
> - 子项生成器函数的调用顺序不一定和数组中的数据项相同,在开发过程中不要假设子项生成器和键值生成器函数是否执行以及执行顺序。如下示例可能无法正常工作: > - 允许子组件生成器函数中包含if/else条件渲染,同时也允许ForEach包含在if/else条件渲染语句中。
> >
> ``` > - itemGenerator函数的调用顺序不一定和数组中的数据项相同,在开发过程中不要假设itemGenerator和keyGenerator函数是否执行及其执行顺序。例如,以下示例可能无法正常工作:
>
> ```ts
> ForEach(anArray.map((item1, index1) => { return { i: index1 + 1, data: item1 }; }), > ForEach(anArray.map((item1, index1) => { return { i: index1 + 1, data: item1 }; }),
> item => Text(`${item.i}. item.data.label`), > item => Text(`${item.i}. item.data.label`),
> item => item.data.id.toString()) > item => item.data.id.toString())
> ``` > ```
## 示例 ## 示例
...@@ -76,49 +76,47 @@ struct MyComponent { ...@@ -76,49 +76,47 @@ struct MyComponent {
@State arr: number[] = [10, 20, 30] @State arr: number[] = [10, 20, 30]
build() { build() {
Column() { Column({ space: 5 }) {
Button() { Button('Reverse Array')
Text('Reverse Array') .onClick(() => {
}.onClick(() => { this.arr.reverse()
this.arr.reverse() })
})
ForEach(this.arr, (item: number) => {
ForEach(this.arr, Text(`item value: ${item}`).fontSize(18)
(item: number) => { Divider().strokeWidth(2)
Text(`item value: ${item}`) }, (item: number) => item.toString())
Divider()
},
(item: number) => item.toString()
)
} }
} }
} }
``` ```
![forEach1](figures/forEach1.gif)
## 数据懒加载 ## 数据懒加载
通过数据懒加载(LazyForEach组件)从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。 通过数据懒加载(LazyForEach)从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。
```ts ```ts
LazyForEach( LazyForEach(
dataSource: IDataSource, dataSource: IDataSource,
itemGenerator: (item: any) => void, itemGenerator: (item: any) => void,
keyGenerator?: (item: any) => string keyGenerator?: (item: any) => string
): void ): void
interface IDataSource { interface IDataSource {
totalCount(): number; totalCount(): number;
getData(index: number): any; getData(index: number): any;
registerDataChangeListener(listener: DataChangeListener): void; registerDataChangeListener(listener: DataChangeListener): void;
unregisterDataChangeListener(listener: DataChangeListener): void; unregisterDataChangeListener(listener: DataChangeListener): void;
} }
interface DataChangeListener { interface DataChangeListener {
onDataReloaded(): void; onDataReloaded(): void;
onDataAdd(index: number): void; onDataAdd(index: number): void;
onDataMove(from: number, to: number): void; onDataMove(from: number, to: number): void;
onDataDelete(index: number): void; onDataDelete(index: number): void;
onDataChange(index: number): void; onDataChange(index: number): void;
} }
``` ```
...@@ -127,19 +125,19 @@ interface DataChangeListener { ...@@ -127,19 +125,19 @@ interface DataChangeListener {
| 参数名 | 参数类型 | 必填 | 参数描述 | | 参数名 | 参数类型 | 必填 | 参数描述 |
| ------------- | --------------------- | ---- | ------------------------------------------------------------ | | ------------- | --------------------- | ---- | ------------------------------------------------------------ |
| dataSource | IDataSource | 是 | 实现IDataSource接口的对象,需要开发者实现相关接口。 | | dataSource | IDataSource | 是 | 实现IDataSource接口的对象,需要开发者实现相关接口。 |
| itemGenerator | (item: any) => void | 是 | 生成子组件的lambda函数,为给定数组项生成一个或多个子组件,单个组件和子组件列表必须括在大括号“{....}”中。 | | itemGenerator | (item: any) => void | 是 | 生成子组件的lambda函数,为数组中的每一个数据项创建一个或多个子组件,单个子组件或子组件列表必须包括在大括号“{...}”中。 |
| keyGenerator | (item: any) => string | 否 | 匿名函数,用于键值生成,为给定数组项生成唯一且稳定的键值。当子项在数组中的位置更改时,子项的键值不得更改,当数组中的子项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能够更好地识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则LazyForEach中的所有节点都将重建。 | | keyGenerator | (item: any) => string | 否 | 匿名函数,用于给数组中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能够更好地识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则LazyForEach中的所有节点都将重建。 |
表2 IDataSource类型说明 ### IDataSource类型说明
| 名称 | 描述 | | 名称 | 描述 |
| ------------------------------------------------------------ | ---------------------- | | ------------------------------------------------------------ | ---------------------- |
| totalCount(): number | 获取数据总数。 | | totalCount(): number | 获取数据总数。 |
| getData(index: number): any | 获取索引对应的数据。 | | getData(index: number): any | 获取索引值index对应的数据。 |
| registerDataChangeListener(listener:DataChangeListener): void | 注册改变数据的控制器。 | | registerDataChangeListener(listener:DataChangeListener): void | 注册数据改变的监听器。 |
| unregisterDataChangeListener(listener:DataChangeListener): void | 注销改变数据的控制器。 | | unregisterDataChangeListener(listener:DataChangeListener): void | 注销数据改变的监听器。 |
表3 DataChangeListener类型说明 ### DataChangeListener类型说明
| 名称 | 描述 | | 名称 | 描述 |
| -------------------------------------------------------- | -------------------------------------- | | -------------------------------------------------------- | -------------------------------------- |
...@@ -156,115 +154,129 @@ interface DataChangeListener { ...@@ -156,115 +154,129 @@ interface DataChangeListener {
## 示例 ## 示例
```ts ```ts
// xxx.ets
class BasicDataSource implements IDataSource { class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [] private listeners: DataChangeListener[] = []
public totalCount(): number { public totalCount(): number {
return 0 return 0
} }
public getData(index: number): any {
return undefined
}
registerDataChangeListener(listener: DataChangeListener): void { public getData(index: number): any {
if (this.listeners.indexOf(listener) < 0) { return undefined
console.info('add listener') }
this.listeners.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener')
this.listeners.splice(pos, 1)
}
}
notifyDataReload(): void { registerDataChangeListener(listener: DataChangeListener): void {
this.listeners.forEach(listener => { if (this.listeners.indexOf(listener) < 0) {
listener.onDataReloaded() console.info('add listener')
}) this.listeners.push(listener)
} }
notifyDataAdd(index: number): void { }
this.listeners.forEach(listener => {
listener.onDataAdd(index) unregisterDataChangeListener(listener: DataChangeListener): void {
}) const pos = this.listeners.indexOf(listener);
} if (pos >= 0) {
notifyDataChange(index: number): void { console.info('remove listener')
this.listeners.forEach(listener => { this.listeners.splice(pos, 1)
listener.onDataChange(index)
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index)
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to)
})
} }
}
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded()
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index)
})
}
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index)
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index)
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to)
})
}
} }
class MyDataSource extends BasicDataSource { class MyDataSource extends BasicDataSource {
private dataArray: string[] = ['/path/image0', '/path/image1', '/path/image2', '/path/image3'] // 初始化数据列表
private dataArray: string[] = ['/path/image0.png', '/path/image1.png', '/path/image2.png', '/path/image3.png']
public totalCount(): number { public totalCount(): number {
return this.dataArray.length return this.dataArray.length
} }
public getData(index: number): any {
return this.dataArray[index]
}
public addData(index: number, data: string): void { public getData(index: number): any {
this.dataArray.splice(index, 0, data) return this.dataArray[index]
this.notifyDataAdd(index) }
}
public pushData(data: string): void { public addData(index: number, data: string): void {
this.dataArray.push(data) this.dataArray.splice(index, 0, data)
this.notifyDataAdd(this.dataArray.length - 1) this.notifyDataAdd(index)
} }
public pushData(data: string): void {
this.dataArray.push(data)
this.notifyDataAdd(this.dataArray.length - 1)
}
} }
@Entry @Entry
@Component @Component
struct MyComponent { struct MyComponent {
private data: MyDataSource = new MyDataSource() private data: MyDataSource = new MyDataSource()
build() {
List({space: 3}) { build() {
LazyForEach(this.data, (item: string) => { List({ space: 3 }) {
ListItem() { LazyForEach(this.data, (item: string) => {
Row() { ListItem() {
Image(item).width("30%").height(50) Row() {
Text(item).fontSize(20).margin({left:10}) Image(item).width(50).height(50)
}.margin({left: 10, right: 10}) Text(item).fontSize(20).margin({ left: 10 })
} }.margin({ left: 10, right: 10 })
.onClick(()=>{
this.data.pushData('/path/image' + this.data.totalCount())
})
}, item => item)
} }
.onClick(() => {
// 每点击一次列表项,数据增加一项
this.data.pushData('/path/image' + this.data.totalCount() + '.png')
})
}, item => item)
} }
}
} }
``` ```
> **说明:** > **说明:**
> >
> - 数据懒加载必须在容器组件内使用,且仅有List、Grid以及Swiper组件支持数据的懒加载(即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次加载所有的数据; > - LazyForEach必须在容器组件内使用,目前仅有List、Grid以及Swiper组件支持数据懒加载(即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。
> >
> - LazyForEach在每次迭代中,必须且只允许创建一个子组件; > - LazyForEach在每次迭代中,必须创建且只允许创建一个子组件。
> >
> - 生成的子组件必须允许在LazyForEach的父容器组件中; > - 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
> >
> - 允许LazyForEach包含在if/else条件渲染语句中,不允许LazyForEach中出现if/else条件渲染语句; > - 允许LazyForEach包含在if/else条件渲染语句中,但不允许LazyForEach中出现if/else条件渲染语句。
> >
> - 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,仅itemGenerator中的UI描述的组件内使用了状态变量时,才会触发组件刷新; > - 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,仅当itemGenerator中创建的子组件内使用了状态变量时,才会触发组件刷新。
> >
> - 子项生成器函数的调用顺序不一定和数据源中的数据项相同,在开发过程中不要假设子项生成器和键值生成器函数是否执行以及执行顺序。如下示例可能无法正常工作: > - itemGenerator函数的调用顺序不一定和数据源中的数据项相同,在开发过程中不要假设itemGenerator和keyGenerator函数是否执行及其执行顺序。例如,以下示例可能无法正常工作:
> >
> ```ts > ```ts
> LazyForEach(dataSource, > LazyForEach(dataSource,
> item => Text(`${item.i}. item.data.label`)), > item => Text(`${item.i}. item.data.label`)),
> item => item.data.id.toString()) > item => item.data.id.toString())
> ``` > ```
\ No newline at end of file
![lazyForEach](figures/lazyForEach.gif)
\ No newline at end of file
...@@ -4,48 +4,49 @@ ...@@ -4,48 +4,49 @@
ArkTS语言的使用在生成器函数中存在一定的限制: ArkTS语言的使用在生成器函数中存在一定的限制:
- 表达式仅允许在字符串(${expression})、if条件、ForEach的参数和组件的参数中使用; - 表达式仅允许在字符串(${expression})、if/else条件语句、ForEach的参数以及组件的参数中使用。
- 任何表达式都不能导致任何应用程序状态变量(@State、@Link、@Prop)的改变,否则会导致未定义和潜在不稳定的框架行为;
- 任何表达式都不能导致应用程序中状态变量(@State、@Link、@Prop)的改变,否则会造成未定义和潜在不稳定的框架行为。
- 生成器函数内部不能有局部变量。 - 生成器函数内部不能有局部变量。
上述限制都不适用于事件处理函数(例如onClick)的匿函数实现。 上述限制都不适用于事件方法(如onClick)的匿名函数实现。
## 变量的双向绑定 ## 变量的双向绑定
ArkTS支持通过$$双向绑定变量,通常应用于状态值频繁改变的变量。 ArkTS支持通过$$双向绑定变量,通常应用于状态值频繁改变的变量。
- 当前$$支持基础类型变量,以及@State、@Link和@Prop装饰的变量。 - 当前$$支持基础类型变量,以及@State、@Link和@Prop装饰的变量。
- 当前$$仅支持[bindPopup](../reference/arkui-ts/ts-universal-attributes-popup.md)属性的show参数和@State变量之间的渲染,Radio组件的checked属性 - 当前$$仅支持[bindPopup](../reference/arkui-ts/ts-universal-attributes-popup.md)属性方法的show参数,[Radio](../reference/arkui-ts/ts-basic-components-radio.md)组件的checked属性,[Refresh](../reference/arkui-ts/ts-container-refresh.md)组件的refreshing参数
- $$绑定的变量变更时,仅渲染当前组件,提高渲染速度。 - $$绑定的变量变化时,仅渲染刷新当前组件,提高渲染速度。
```ts ```ts
// xxx.ets // xxx.ets
@Entry @Entry
@Component @Component
struct bindPopup { struct bindPopupPage {
@State customPopup: boolean = false @State customPopup: boolean = false
build() {
column() {
button() {
Text('Popup')
}
.onClick(() => {
this.customPopup = !this.customPopup
})
.bindPopup(
$$this.customPopup, {
mesage: "showPopup"
}
)
}
}
}
build() {
Column() {
Button('Popup')
.margin(20)
.onClick(() => {
this.customPopup = !this.customPopup
})
.bindPopup($$this.customPopup, {
message: "showPopup"
})
}
}
}
``` ```
## 状态变量多种数据类型声明使用限制 ![popup](figures/popup.gif)
## 状态变量数据类型声明使用限制
@State、@Provide、 @Link和@Consume四种状态变量的多种数据类型只能同时由简单数据类型或引用数据类型其中一种构成。 @State、@Provide、 @Link和@Consume四种状态变量的数据类型声明只能由简单数据类型或引用数据类型的其中一种构成。
示例: 示例:
...@@ -53,14 +54,14 @@ struct bindPopup { ...@@ -53,14 +54,14 @@ struct bindPopup {
// xxx.ets // xxx.ets
@Entry @Entry
@Component @Component
struct Index { struct IndexPage {
//错误写法: @State message: string | Resource = 'Hello World' //错误写法: @State message: string | Resource = 'Hello World'
@State message: string = 'Hello World' @State message: string = 'Hello World'
build() { build() {
Row() { Row() {
Column() { Column() {
Text(`${ this.message }`) Text(`${this.message}`)
.fontSize(50) .fontSize(50)
.fontWeight(FontWeight.Bold) .fontWeight(FontWeight.Bold)
} }
...@@ -69,4 +70,66 @@ struct Index { ...@@ -69,4 +70,66 @@ struct Index {
.height('100%') .height('100%')
} }
} }
``` ```
\ No newline at end of file
![hello](figures/hello.PNG)
## 自定义组件成员变量初始化的方式与约束
组件的成员变量可以通过两种方式初始化:
- 本地初始化:
```ts
@State counter: Counter = new Counter()
```
- 在构造组件时通过构造参数初始化:
```ts
MyComponent({counter: $myCounter})
```
具体允许哪种方式取决于状态变量的装饰器:
| 装饰器类型 | 本地初始化 | 通过构造函数参数初始化 |
| ------------ | ----- | ----------- |
| @State | 必须 | 可选 |
| @Prop | 禁止 | 必须 |
| @Link | 禁止 | 必须 |
| @StorageLink | 必须 | 禁止 |
| @StorageProp | 必须 | 禁止 |
| @Provide | 必须 | 可选 |
| @Consume | 禁止 | 禁止 |
| @ObjectLink | 禁止 | 必须 |
| 常规成员变量 | 推荐 | 可选 |
从上表中可以看出:
- @State变量需要本地初始化,初始化的值可以被构造参数覆盖。
- @Prop和@Link变量必须且仅通过构造函数参数进行初始化。
通过构造函数方法初始化成员变量,需要遵循如下规则:
| 从父组件中的变量(下)到子组件中的变量(右) | @State | @Link | @Prop | 常规变量 |
| ---------------------- | ------ | ----- | ----- | ---- |
| @State | 不允许 | 允许 | 允许 | 允许 |
| @Link | 不允许 | 允许 | 不推荐 | 允许 |
| @Prop | 不允许 | 不允许 | 允许 | 允许 |
| @StorageLink | 不允许 | 允许 | 不允许 | 允许 |
| @StorageProp | 不允许 | 不允许 | 不允许 | 允许 |
| 常规变量 | 允许 | 不允许 | 不允许 | 允许 |
从上表中可以看出:
- 父组件的常规变量可以用于初始化子组件的@State变量,但不能用于初始化@Link或@Prop变量。
- 父组件的@State变量可以初始化子组件的@Prop、@Link(通过$)或常规变量,但不能初始化子组件的@State变量。
- 父组件的@Link变量可以初始化子组件的@Link或常规变量。但是初始化子组件的@State成员是语法错误,此外不建议初始化@Prop。
- 父组件的@Prop变量可以初始化子组件的常规变量或@Prop变量,但不能初始化子组件的@State或@Link变量。
- @StorageLink和@StorageProp不允许由父组件中传递到子组件。
- 除了上述规则外,还需要遵循TS的强类型规则。
\ No newline at end of file
# 应用级变量的状态管理 # 应用级变量的状态管理
在前面的章节中,已经讲述了如何管理页面级变量的状态,本章将说明如何管理应用级变量的状态。 在前面的章节中,已经讲述了如何管理页面级变量的状态,本章将说明如何管理应用级变量的状态,具体接口说明请参考[应用级变量的状态管理接口](../reference/arkui-ts/ts-state-management.md)
## AppStorage ## AppStorage
AppStorage是应用程序中的单例对象,由UI框架在应用程序启动时创建,在应用程序退出时销毁,为应用程序范围内的可变状态属性提供中央存储。 [AppStorage](../reference/arkui-ts/ts-state-management.md#appstorage)是应用程序中的单例对象,由UI框架在应用程序启动时创建,在应用程序退出时销毁,为应用程序范围内的可变状态属性提供中央存储。
AppStorage包含整个应用程序中需要访问的所有状态属性,只要应用程序保持运行,AppStorage就会保存所有属性及属性值,属性值可以通过唯一的键值进行访问。 AppStorage包含整个应用程序中需要访问的所有状态属性,只要应用程序保持运行,AppStorage就会保存所有属性及属性值,属性值可以通过唯一的键值进行访问。
组件可以通过装饰器将应用程序状态数据与AppStorage进行同步,应用业务逻辑的实现也可以通过接口访问AppStorage。 组件可以通过装饰器将应用程序状态数据与AppStorage进行同步,应用业务逻辑的实现也可以通过接口访问AppStorage。
AppStorage的选择状态属性可以与不同的数据源或数据接收器同步,这些数据源和接收器可以是设备上的本地或远程,并具有不同的功能,如数据持久性。这样的数据源和接收器可以独立于UI在业务逻辑中实现。 AppStorage的选择状态属性可以与不同的数据源或数据接收器同步,这些数据源和接收器可以是设备上的本地或远程,并具有不同的功能,如数据持久性。这样的数据源和接收器可以独立于UI在业务逻辑中实现。
默认情况下,AppStorage中的属性是可变的,AppStorage还可使用不可变(只读)属性。 默认情况下,AppStorage中的属性是可变的,AppStorage还可使用不可变(只读)属性。
AppStorage的具体接口请参考[状态管理](../reference/arkui-ts/ts-state-management.md) > **说明**:[Worker](../reference/apis/js-apis-worker.md)和主线程只能通过[postMessage](../reference/apis/js-apis-worker.md#postmessage)交互,不能使用AppStorage进行交互
### @StorageLink装饰器 ### @StorageLink装饰器
...@@ -24,54 +22,59 @@ AppStorage的选择状态属性可以与不同的数据源或数据接收器同 ...@@ -24,54 +22,59 @@ AppStorage的选择状态属性可以与不同的数据源或数据接收器同
### @StorageProp装饰器 ### @StorageProp装饰器
组件通过使用@StorageProp(key)装饰的状态变量,将与AppStorage建立单向数据绑定,key标识AppStorage中的属性键值。当创建包含@StoageProp的状态变量的组件时,该状态变量的值将使用AppStorage中的值进行初始化。AppStorage中的属性值的更改会导致绑定的UI组件进行状态更新。 组件通过使用@StorageProp(key)装饰的状态变量,与AppStorage建立单向数据绑定,key标识AppStorage中的属性键值。当创建包含@StorageProp的状态变量的组件时,该状态变量的值将使用AppStorage中的值进行初始化。AppStorage中属性值的更改会导致绑定该状态变量的UI组件进行状态更新。
## 示例 ### 示例
每次用户单击Count按钮时,this.varA变量值都会增加,此变量与AppStorage中的varA同步。每次用户单击当前语言按钮时,修改AppStorage中的languageCode,此修改会同步给this.lang变量。 每次用户单击Count按钮时,this.varA变量值都会增加1,此变量与AppStorage中的varA同步。每次用户单击language按钮时,修改AppStorage中的languageCode,此修改会同步给this.languageCode变量。
```ts ```ts
// xxx.ets // xxx.ets
@Entry @Entry
@Component @Component
struct ComponentA { struct ComponentA {
@StorageLink('varA') varA: number = 2 @StorageLink('varA') varA: number = 2
@StorageProp('languageCode') lang: string = 'en' @StorageProp('languageCode') languageCode: string = 'en'
private label: string = 'count' private label: string = 'count'
aboutToAppear() { aboutToAppear() {
this.label = (this.lang === 'zh') ? '' : 'Count' this.label = (this.languageCode === 'zh') ? '数量' : 'Count'
} }
build() { build() {
Column(){ Column() {
Row({ space: 20 }) { Row({ space: 20 }) {
Button(`${this.label}: ${this.varA}`) Button(`${this.label}: ${this.varA}`)
.onClick(() => { .onClick(() => {
AppStorage.Set<number>('varA', AppStorage.Get<number>('varA') + 1) AppStorage.Set<number>('varA', AppStorage.Get<number>('varA') + 1)
}) })
Button(`lang: ${this.lang}`) Button(`language: ${this.languageCode}`)
.onClick(() => { .onClick(() => {
if (this.lang === 'zh') { if (AppStorage.Get<string>('languageCode') === 'zh') {
AppStorage.Set<string>('languageCode', 'en') AppStorage.Set<string>('languageCode', 'en')
} else { } else {
AppStorage.Set<string>('languageCode', 'zh') AppStorage.Set<string>('languageCode', 'zh')
} }
this.label = (this.lang === 'zh') ? '' : 'Count' this.label = (this.languageCode === 'zh') ? '数量' : 'Count'
}) })
} }
.margin({ bottom: 50 }) .margin({ bottom: 50 })
Row(){
Button(`更改@StorageLink修饰的变量:${this.varA}`).fontSize(10) Row() {
Button(`更改@StorageLink修饰的变量:${this.varA}`).height(40).fontSize(14)
.onClick(() => { .onClick(() => {
this.varA++ this.varA++
}) })
}.margin({ bottom: 50 }) }.margin({ bottom: 50 })
Row(){
Button(`更改@StorageProp修饰的变量:${this.lang}`).fontSize(10) Row() {
Button(`更改@StorageProp修饰的变量:${this.languageCode}`).height(40).fontSize(14)
.onClick(() => { .onClick(() => {
this.lang = 'test' if (this.languageCode === 'zh') {
this.languageCode = 'en'
} else {
this.languageCode = 'zh'
}
}) })
} }
} }
...@@ -79,176 +82,200 @@ struct ComponentA { ...@@ -79,176 +82,200 @@ struct ComponentA {
} }
``` ```
![appstorage](figures/appstorage.gif) ![appstorage](figures/appstorage.gif)
## LocalStorage ## LocalStorage
> **说明:** 该组件从API version 9开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 > **说明:**
>
> 该接口从API version 9开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
LocalStorage是应用程序中的存储单元,生命周期跟随其关联的Ability。LocalStorage为应用程序范围内的可变状态属性和非可变状态属性提供存储,可变状态属性和非可变状态属性是构建应用程序UI的一部分,如一个Ability的UI。 [LocalStorage](../reference/arkui-ts/ts-state-management.md#localstorage9)是应用程序中的存储单元,生命周期跟随其关联的Ability。LocalStorage为应用程序范围内的可变状态属性和非可变状态属性提供存储,可变状态属性和非可变状态属性是构建应用程序UI的一部分,如一个Ability的UI。
应用层:一个应用程序可以创建多个LocalStorage实例,应用程序的每一个Ability对应一个LocalStorage实例。 应用层:一个应用程序可以创建多个LocalStorage实例,应用程序的每一个Ability对应一个LocalStorage实例。
Ability: 一个应用程序可以拥有多个Ability,一个Ability中的所有子组件最多可以分配一个LocalStorage实例。并且,Ability中的所有子组件都将继承对此LocalStorage实例存储对象的访问权。 Ability:一个应用程序可以拥有多个Ability,一个Ability中的所有子组件最多可以分配一个LocalStorage实例。并且,Ability中的所有子组件都将继承对此LocalStorage实例存储对象的访问权。
一个组件最多可以访问一个LocalStorage实例,一个LocalStorage对象可以分配给多个组件。 一个组件最多可以访问一个LocalStorage实例,一个LocalStorage对象可以分配给多个组件。
LocalStorage的具体接口请参考[状态管理](../reference/arkui-ts/ts-state-management.md) ### @LocalStorageLink装饰器
## @LocalStorageLink装饰器
组件通过使用@LocalStorageLink(key)装饰的状态变量,key值为LocalStorage中的属性键值,与LocalStorage建立双向数据绑定。当创建包含@LocalStorageLink的状态变量的组件时,该状态变量的值将会使用LocalStorage中的值进行初始化。如果LocalStorage中未定义初始值,将使用@LocalStorageLink定义的初始值。在UI组件中对@LocalStorageLink的状态变量所做的更改将同步到LocalStorage中,并从LocalStorage同步到Ability下的组件中。 组件通过使用@LocalStorageLink(key)装饰的状态变量,key值为LocalStorage中的属性键值,与LocalStorage建立双向数据绑定。当创建包含@LocalStorageLink的状态变量的组件时,该状态变量的值将会使用LocalStorage中的值进行初始化。如果LocalStorage中未定义初始值,将使用@LocalStorageLink定义的初始值。在UI组件中对@LocalStorageLink的状态变量所做的更改将同步到LocalStorage中,并从LocalStorage同步到Ability下的组件中。
## @LocalStorageProp装饰器 ### @LocalStorageProp装饰器
组件通过使用LocalStorageProp(key)装饰的状态变量,key值为LocalStorage中的属性键值,与LocalStorage建立单向数据绑定。当创建包含@LocalStorageProp的状态变量的组件时,该状态变量的值将使用LocalStorage中的值进行初始化。LocalStorage中的属性值的更改会导致当前Ability下的所有UI组件进行状态更新。 组件通过使用LocalStorageProp(key)装饰的状态变量,key值为LocalStorage中的属性键值,与LocalStorage建立单向数据绑定。当创建包含@LocalStorageProp的状态变量的组件时,该状态变量的值将使用LocalStorage中的值进行初始化。LocalStorage中的属性值的更改会导致当前Ability下的所有UI组件进行状态更新。
> **说明:** 创建LocalStorage实例时如未定义初始值,可以使用组件内@LocalStorageLink和@LocalStorageProp的初始值。如果定义时给定了初始值,那么不会再使用@LocalStorageLink和@LocalStorageProp的初始值。 > **说明:** 创建LocalStorage实例时如未定义初始值,可以使用组件内@LocalStorageLink和@LocalStorageProp的初始值。如果定义时给定了初始值,那么不会再使用@LocalStorageLink和@LocalStorageProp的初始值。
### 示例1(在一个Ability创建的LocalStorage) ### 示例1(在一个Ability中创建LocalStorage)
LocalStorage通过loadContent接口加载,接口详见[loadContent](../reference/apis/js-apis-window.md#loadcontent9-1) LocalStorage通过loadContent接口加载,接口说明详见[loadContent](../reference/apis/js-apis-window.md#loadcontent9-1)
``` ```ts
// MainAbility.ts
import Ability from '@ohos.application.Ability' import Ability from '@ohos.application.Ability'
export default class MainAbility extends Ability {
storage : LocalStorage export default class MainAbility extends Ability {
onCreate() { storage: LocalStorage
this.storage = new LocalStorage();
this.storage.setOrCreate("storageSimpleProp",121); onCreate() {
console.log("[Demo MainAbility onCreate]"); this.storage = new LocalStorage()
} this.storage.setOrCreate('storageSimpleProp', 121)
onDestroy() { console.info('[Demo MainAbility onCreate]')
console.log("[Demo MainAbility onDestroy]") }
}
onWindowStageCreate(windowStage) { onDestroy() {
// storage作为参数传递给loadContent接口。 console.info('[Demo MainAbility onDestroy]')
windowStage.loadContent("pages/index",this.storage) }
}
onWindowStageDestroy() { onWindowStageCreate(windowStage) {
console.log("[Demo] MainAbility onWindoeStageDestroy") // storage作为参数传递给loadContent接口
} windowStage.loadContent('pages/index', this.storage)
onForeground() { }
console.log("[Demo] MainAbility onForeground")
} onWindowStageDestroy() {
onBackground() { console.info('[Demo] MainAbility onWindowStageDestroy')
console.log("[Demo] MainAbility onBackground") }
}
onForeground() {
console.info('[Demo] MainAbility onForeground')
}
onBackground() {
console.info('[Demo] MainAbility onBackground')
}
} }
``` ```
@Component组件获取数据 @Component组件获取数据
``` ```ts
// index.ets
let storage = LocalStorage.GetShared() let storage = LocalStorage.GetShared()
@Entry(storage) @Entry(storage)
@Component @Component
struct LocalStorageComponent { struct LocalStorageComponent {
@LocalStorageLink("storageSimpleProp") simpleVarName: number = 0 @LocalStorageLink('storageSimpleProp') simpleVarName: number = 0
build() { build() {
Column(){ Column() {
Button(`LocalStorageLink: ${ this.simpleVarName.toString() }`) Button(`LocalStorageLink: ${this.simpleVarName.toString()}`)
.onClick(()=>{ .margin(20)
this.simpleVarName +=1; .onClick(() => {
this.simpleVarName += 1
}) })
Text(JSON.stringify(this.simpleVarName)) Text(JSON.stringify(this.simpleVarName))
.fontSize(50) .fontSize(50)
LocalStorageComponentProp(); LocalStorageComponentProp()
} }.width('100%')
.height(500)
} }
} }
@Component @Component
struct LocalStorageComponentProp { struct LocalStorageComponentProp {
@LocalStorageProp("storageSimpleProp") simpleVarName: number = 0 @LocalStorageProp('storageSimpleProp') simpleVarName: number = 0
build() { build() {
Column() { Column() {
Button(`LocalStorageProp: ${ this.simpleVarName.toString() }`) Button(`LocalStorageProp: ${this.simpleVarName.toString()}`)
.margin(20)
.onClick(() => { .onClick(() => {
this.simpleVarName += 1; this.simpleVarName += 1
}) })
Text(JSON.stringify(this.simpleVarName)) Text(JSON.stringify(this.simpleVarName))
.fontSize(50) .fontSize(50)
} }.width('100%')
.height(500)
} }
} }
``` ```
![appstorage1](figures/appstorage1.gif)
### 示例2(在Entry页面定义LocalStorage) ### 示例2(在Entry页面定义LocalStorage)
``` ```ts
let storage = new LocalStorage({"PropA":47}); // xxx.ets
let storage = new LocalStorage({ "PropA": 47 })
@Entry(storage) @Entry(storage)
@Component @Component
struct ComA { struct ComA {
@LocalStorageLink("PropA") storLink: number = 1; @LocalStorageLink("PropA") storageLink: number = 1
build() {
Column() { build() {
Text(`Parent from LocalStorage ${ this.storLink }`) Column() {
.onClick(()=>this.storLink+=1) Text(`Parent from LocalStorage ${this.storageLink}`)
Child() .fontSize(18)
} .margin(20)
.onClick(() => this.storageLink += 1)
Child()
}
} }
} }
@Component @Component
struct Child{ struct Child {
@LocalStorageLink("PropA") storLink: number = 1; @LocalStorageLink("PropA") storageLink: number = 1
build() {
Text(`Parent from LocalStorage ${ this.storLink }`) build() {
.onClick(()=>this.storLink+=1) Text(`Child from LocalStorage ${this.storageLink}`)
.fontSize(18)
.margin(20)
.onClick(() => this.storageLink += 1)
} }
} }
``` ```
![appstorage2](figures/appstorage2.gif)
## PersistentStorage ## PersistentStorage
PersistentStorage类提供了一些静态方法用来管理应用持久化数据,可以将特定标记的持久化数据链接到AppStorage中,并由AppStorage接口访问对应持久化数据,或者通过@StorageLink装饰器来访问对应key的变量。 [PersistentStorage](../reference/arkui-ts/ts-state-management.md#persistentstorage)提供了一些静态方法用来管理应用持久化数据,可以将特定标记的持久化数据链接到AppStorage中,并由AppStorage接口访问对应持久化数据,或者通过@StorageLink装饰器来访问对应key的变量。
> **说明:** > **说明:**
> >
> - PersistProp接口使用时,需要保证输入对应的key应当在AppStorage存在。 > - PersistentStorage的PersistProp接口使用时,需要保证输入对应的key在AppStorage中存在。
> - DeleteProp接口使用时,只能对本次启动已经link过的数据生效。 > - PersistentStorage的DeleteProp接口使用时,只能对本次应用启动时已经link过的数据生效。
```ts ```ts
// xxx.ets // xxx.ets
PersistentStorage.PersistProp("highScore", "0"); PersistentStorage.PersistProp('highScore', '0')
@Entry @Entry
@Component @Component
struct PersistentComponent { struct PersistentComponent {
@StorageLink('highScore') highScore: string = '0' @StorageLink('highScore') highScore: string = '0'
@State currentScore: number = 0 @State currentScore: number = 0
build() {
Column() { build() {
if (this.currentScore === Number(this.highScore)) { Column() {
Text(`new highScore : ${this.highScore}`) if (this.currentScore === Number(this.highScore)) {
} Text(`new highScore : ${this.highScore}`).fontSize(18)
Button() { }
Text(`goal!, currentScore : ${this.currentScore}`) Button(`goal!, currentScore : ${this.currentScore}`)
.fontSize(10) .margin(20)
}.onClick(() => { .onClick(() => {
this.currentScore++ this.currentScore++
if (this.currentScore > Number(this.highScore)) { if (this.currentScore > Number(this.highScore)) {
this.highScore = this.currentScore.toString() this.highScore = this.currentScore.toString()
} }
}) })
} }.width('100%')
} }
} }
``` ```
![PersistentStorage](figures/PersistentStorage.gif) ![appstorage3](figures/appstorage3.gif)
## Environment ## Environment
Environment是框架在应用程序启动时创建的单例对象,它为AppStorage提供了一系列应用程序需要的环境状态属性,这些属性描述了应用程序运行的设备环境。Environment及其属性是不可变的,所有属性值类型均为简单类型。如下示例展示了从Environment获取语音环境 [Environment](../reference/arkui-ts/ts-state-management.md#environment)是框架在应用程序启动时创建的单例对象,它为AppStorage提供了一系列应用程序需要的环境状态数据,这些数据描述了应用程序运行的设备环境,包括系统语言、深浅色模式等等。Environment及其属性是不可变的,所有数据类型均为简单类型。如下示例展示了从Environment获取系统是否开启无障碍屏幕朗读
``` ```ts
Environment.EnvProp("accessibilityEnabled", "default"); Environment.EnvProp('accessibilityEnabled', 'default')
var enable = AppStorage.Get("accessibilityEnabled"); var enable = AppStorage.Get('accessibilityEnabled')
``` ```
accessibilityEnabled是Environment提供默认系统变量识别符。首先需要将对应系统属性绑定到AppStorage中,再通过AppStorage中的方法或者装饰器访问对应系统的属性数据。 accessibilityEnabled是Environment提供的系统默认变量识别符。首先需要将对应系统属性绑定到AppStorage上,再通过AppStorage中的方法或者装饰器访问对应的系统属性数据。
\ No newline at end of file
# 基本概念 # 基本概念
ArkTS提供了多维度的状态管理机制,在UI开发框架中,和UI相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间、爷孙组件之前,也可以时全局范围内的传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活的利用这些能力来实现数据和UI的联动。 ArkTS提供了多维度的状态管理机制,在ArkUI开发框架中,和UI相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,也可以是应用全局范围内的传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活地利用这些能力来实现数据和UI的联动。
![](figures/CoreSpec_figures_state-mgmt-overview.png) ![](figures/CoreSpec_figures_state-mgmt-overview.png)
...@@ -11,20 +11,20 @@ ArkTS提供了多维度的状态管理机制,在UI开发框架中,和UI相 ...@@ -11,20 +11,20 @@ ArkTS提供了多维度的状态管理机制,在UI开发框架中,和UI相
| 装饰器 | 装饰内容 | 说明 | | 装饰器 | 装饰内容 | 说明 |
| ----------- | ------------------------- | ------------------------------------------------------------ | | ----------- | ------------------------- | ------------------------------------------------------------ |
| @State | 基本数据类型,类,数组 | 修饰的状态数据被修改时会触发组件的build方法进行UI界面更新。 | | @State | 基本数据类型,类,数组 | 修饰的状态数据被修改时会触发组件的build方法进行UI界面更新。 |
| @Prop | 基本数据类型 | 修改后的状态数据用于在父组件和子组件之间建立单向数据依赖关系。修改父组件关联数据时,更新当前组件的UI。 | | @Prop | 基本数据类型 | 修改后的状态数据用于在父组件和子组件之间建立单向数据依赖关系。修改父组件关联数据时,当前组件会重新渲染。 |
| @Link | 基本数据类型,类,数组 | 父子组件之间的双向数据绑定,父组件的内部状态数据作为数据源,任何一方所做的修改都会反映给另一方。 | | @Link | 基本数据类型,类,数组 | 父子组件之间的双向数据绑定,父组件的内部状态数据作为数据源,任何一方所做的修改都会反映给另一方。 |
| @Observed | 类 | @Observed应用于类,表示该类中的数据变更被UI页面管理。 | | @Observed | 类 | @Observed应用于类,表示该类中的数据变更被UI页面管理。 |
| @ObjectLink | 被@Observed所装饰类的对象 | 装饰的状态数据被修改时,在父组件或者其他兄弟组件内与它关联的状态数据所在的组件都会更新UI。 | | @ObjectLink | 被@Observed所装饰类的对象 | @ObjectLink装饰的状态数据被修改时,在父组件或者其他兄弟组件内与它关联的状态数据所在的组件都会重新渲染。 |
| @Consume | 基本数据类型,类,数组 | @Consume装饰的变量在感知到@Provide装饰的变量更新后,会触发当前自定义组件的重新渲染。 | | @Consume | 基本数据类型,类,数组 | @Consume装饰的变量在感知到@Provide装饰的变量更新后,会触发当前自定义组件的重新渲染。 |
| @Provide | 基本数据类型,类,数组 | @Provide作为数据的提供方,可以更新其子孙节点的数据,并触发页面渲染。 | | @Provide | 基本数据类型,类,数组 | @Provide作为数据的提供方,可以更新其子孙节点的数据,并触发页面重新渲染。 |
## 应用级变量的状态管理 ## 应用级变量的状态管理
AppStorage是整个UI应用程序状态的中心“数据库”,UI框架会针对应用程序创建单例AppStorage对象,并提供相应的装饰器和接口供应用程序使用。 AppStorage是整个应用程序状态的中心“数据库”,UI框架会针对应用程序创建单例AppStorage对象,并提供相应的装饰器和接口供应用程序使用。
- @StorageLink:@StorageLink(name)的原理类似于@Consume(name),不同的是,该给定名称的链接对象是从AppStorage中获得的,在UI组件和AppStorage之间建立双向绑定同步数据。 - @StorageLink:@StorageLink(name)的原理类似于@Consume(name),不同的是,该给定名称的链接对象是从AppStorage中获得的,在UI组件和AppStorage之间建立双向绑定同步数据。
- @StorageProp:@StorageProp(name)将UI组件属性与AppStorage进行单向同步,AppStorage中值的更改会更新组件中的属性,但UI组件无法更改AppStorage中的属性值 - @StorageProp:@StorageProp(name)将UI组件数据与AppStorage进行单向同步,AppStorage中值的更改会更新UI组件中的数据,但UI组件无法更改AppStorage中的数据
- AppStorage还提供用于业务逻辑实现的API,用于添加、读取、修改和删除应用程序的状态属性,此API所做的更改会导致修改的状态数据同步到UI组件上进行UI更新。 - AppStorage还提供了用于业务逻辑实现的API,用于添加、读取、修改和删除应用程序的状态数据,此API所做的更改会导致修改的状态数据同步到UI组件上进行UI更新。
- PersistentStorage类提供了一些静态方法用来管理应用持久化数据,可以将特定标记的持久化数据链接到AppStorage中,并由AppStorage接口访问对应持久化数据,或者通过@StorageLink装饰器来访问对应key的变量。 - PersistentStorage提供了一些静态方法用来管理应用持久化数据,可以将特定标记的持久化数据链接到AppStorage中,并由AppStorage接口访问对应持久化数据,或者通过@StorageLink装饰器来访问对应key的变量。
- Environment是框架在应用程序启动时创建的单例对象,它为AppStorage提供了一系列应用程序需要的环境状态属性,这些属性描述了应用程序运行的设备环境。 - Environment是框架在应用程序启动时创建的单例对象,它为AppStorage提供了一系列应用程序需要的环境状态数据,这些数据描述了应用程序运行的设备环境。
...@@ -2,188 +2,199 @@ ...@@ -2,188 +2,199 @@
@State、@Prop、@Link、@Provide、Consume、@ObjectLink、@Observed和@Watch用于管理页面级变量的状态。 @State、@Prop、@Link、@Provide、Consume、@ObjectLink、@Observed和@Watch用于管理页面级变量的状态。
请参考[状态变量多种数据类型声明的使用限制](./arkts-restrictions-and-extensions.md)了解@State、@Provide、 @Link和@Consume四种状态变量的约束条件。
## @State ## @State
@State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。 @State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。
@State状态数据具有以下特征: @State状态数据具有以下特征:
- 支持多种类型数据:支持class、number、boolean、string强类型数据的值类型和引用类型,以及这些强类型构成的数组,即Array&lt;class&gt;、Array&lt;string&gt;、Array&lt;boolean&gt;、Array&lt;number&gt;。不支持object和any。
- 支持多种类型:允许class、number、boolean、string强类型的按值和按引用类型。允许这些强类型构成的数组,即Array&lt;class&gt;、Array&lt;string&gt;、Array&lt;boolean&gt;、Array&lt;number&gt;。不允许object和any。
- 支持多实例:组件不同实例的内部状态数据独立。 - 支持多实例:组件不同实例的内部状态数据独立。
- 内部私有:标记为@State的属性是私有变量,只能在组件内访问。 - 内部私有:标记为@State的属性是私有变量,只能在组件内访问。
- 需要本地初始化:必须为所有@State变量分配初始值,将变量保持未初始化可能导致框架行为未定义 - 需要本地初始化:必须为所有@State变量分配初始值,变量未初始化可能导致未定义的框架异常行为
- 创建自定义组件时支持通过状态变量名设置初始值:在创建组件实例时,可以通过变量名显式指定@State状态属性的初始值。 - 创建自定义组件时支持通过状态变量名设置初始值:在创建组件实例时,可以通过变量名显式指定@State状态变量的初始值。
**示例:** **示例:**
在下面的示例中: 在下面的示例中:
- 用户定义的组件MyComponent定义了@State状态变量count和title。如果count或title的值发生变化,则执行MyComponent的build方法来重新渲染组件; - 用户定义的组件MyComponent定义了@State状态变量count和title。如果count或title的值发生变化,则执行MyComponent的build方法来重新渲染组件;
- EntryComponent中有多个MyComponent组件实例,第一个MyComponent内部状态的更改不会影响第二个MyComponent; - EntryComponent中有多个MyComponent组件实例,第一个MyComponent内部状态的更改不会影响第二个MyComponent;
- 创建MyComponent实例时通过变量名给组件内的变量进行初始化,如: - 创建MyComponent实例时通过变量名给组件内的变量进行初始化,如:
```ts ```ts
MyComponent({title: {value: 'Hello, World 2'}, count: 7}) MyComponent({ title: { value: 'Hello World 2' }, count: 7 })
``` ```
```ts ```ts
// xxx.ets // xxx.ets
class Model { class Model {
value: string value: string
constructor(value: string) {
this.value = value constructor(value: string) {
} this.value = value
}
} }
@Entry @Entry
@Component @Component
struct EntryComponent { struct EntryComponent {
build() { build() {
Column() { Column() {
MyComponent({count: 1, increaseBy: 2}) // MyComponent1 in this document MyComponent({ count: 1, increaseBy: 2 }) // 第1个MyComponent实例
MyComponent({title: {value: 'Hello, World 2'}, count: 7}) //MyComponent2 in this document MyComponent({ title: { value: 'Hello World 2' }, count: 7 }) // 第2个MyComponent实例
}
} }
}
} }
@Component @Component
struct MyComponent { struct MyComponent {
@State title: Model = {value: 'Hello World'} @State title: Model = { value: 'Hello World' }
@State count: number = 0 @State count: number = 0
private toggle: string = 'Hello World' private toggle: string = 'Hello World'
private increaseBy: number = 1 private increaseBy: number = 1
build() { build() {
Column() { Column() {
Text(`${this.title.value}`).fontSize(30) Text(`${this.title.value}`).fontSize(30)
Button() { Button('Click to change title')
Text(`Click to change title`).fontSize(20).fontColor(Color.White) .margin(20)
}.onClick(() => { .onClick(() => {
this.title.value = (this.toggle == this.title.value) ? 'Hello World' : 'Hello UI' // 修改内部状态变量title
}) // Modify the internal state of MyComponent using the anonymous method. this.title.value = (this.toggle == this.title.value) ? 'Hello World' : 'Hello ArkUI'
})
Button() {
Text(`Click to increase count=${this.count}`).fontSize(20).fontColor(Color.White) Button(`Click to increase count=${this.count}`)
}.onClick(() => { .margin(20)
this.count += this.increaseBy .onClick(() => {
}) // Modify the internal state of MyComponent using the anonymous method. // 修改内部状态变量count
} this.count += this.increaseBy
})
} }
}
} }
``` ```
![@state1](figures/@state.png)
## @Prop ## @Prop
@Prop与@State有相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但更改不会通知给父组件,即@Prop属于单向数据绑定。 @Prop与@State有相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但变量的更改不会通知给父组件,即@Prop属于单向数据绑定。
@Prop状态数据具有以下特征: @Prop状态数据具有以下特征:
- 支持简单类型:仅支持number、string、boolean简单类型; - 支持简单类型:仅支持number、string、boolean等简单数据类型;
- 私有:仅组件内访问; - 私有:仅支持组件内访问;
- 支持多个实例:一个组件中可以定义多个标有@Prop的属性; - 支持多个实例:一个组件中可以定义多个标有@Prop的属性;
- 创建自定义组件时将值传递给@Prop变量进行初始化:在创建组件的新实例时,必须初始化所有@Prop变量,不支持在组件内部进行初始化。 - 创建自定义组件时将值传递给@Prop变量进行初始化:在创建组件的新实例时,必须初始化所有@Prop变量,不支持在组件内部进行初始化。
**示例:** **示例:**
在下面的示例中,当按“+1”或“-1”按钮时,父组件状态发生变化,重新执行build方法,此时将创建一个新的CountDownComponent组件。父组件的countDownStartValue状态属性被用于初始化子组件的@Prop变量,当按下子组件的“Try again”按钮时,其@Prop变量count将被更改,CountDownComponent重新渲染。但是count值的更改不会影响父组件的countDownStartValue值。 在下面的示例中,当按“+1”或“-1”按钮时,父组件状态发生变化,重新执行build方法,此时将创建一个新的CountDownComponent组件实例。父组件的countDownStartValue状态变量被用于初始化子组件的@Prop变量,当按下子组件的“count - costOfOneAttempt”按钮时,其@Prop变量count将被更改,CountDownComponent重新渲染,但是count值的更改不会影响父组件的countDownStartValue值。
```ts ```ts
// xxx.ets // xxx.ets
@Entry @Entry
@Component @Component
struct ParentComponent { struct ParentComponent {
@State countDownStartValue: number = 10 // 10 Nuggets default start value in a Game @State countDownStartValue: number = 10 // 初始化countDownStartValue
build() {
Column() { build() {
Text(`Grant ${this.countDownStartValue} nuggets to play.`) Column() {
Button() { Text(`Grant ${this.countDownStartValue} nuggets to play.`).fontSize(18)
Text('+1 - Nuggets in New Game') Button('+1 - Nuggets in New Game')
}.onClick(() => { .margin(15)
this.countDownStartValue += 1 .onClick(() => {
}) this.countDownStartValue += 1
Button() { })
Text('-1 - Nuggets in New Game')
}.onClick(() => { Button('-1 - Nuggets in New Game')
this.countDownStartValue -= 1 .margin(15)
}) .onClick(() => {
// 创建子组件时,必须在构造函数参数中提供其@Prop变量的初始值,同时初始化常规变量CostOfOneAttump(非Prop) this.countDownStartValue -= 1
CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2}) })
} // 创建子组件时,必须在构造函数参数中提供其@Prop变量count的初始值,同时初始化常规变量costOfOneAttempt(非Prop变量)
CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 })
} }
}
} }
@Component @Component
struct CountDownComponent { struct CountDownComponent {
@Prop count: number @Prop count: number
private costOfOneAttempt: number private costOfOneAttempt: number
build() { build() {
Column() { Column() {
if (this.count > 0) { if (this.count > 0) {
Text(`You have ${this.count} Nuggets left`) Text(`You have ${this.count} Nuggets left`).fontSize(18)
} else { } else {
Text('Game over!') Text('Game over!').fontSize(18)
} }
Button() { Button('count - costOfOneAttempt')
Text('Try again') .margin(15)
}.onClick(() => { .onClick(() => {
this.count -= this.costOfOneAttempt this.count -= this.costOfOneAttempt
}) })
}
} }
}
} }
``` ```
> **说明:** 创建新组件实例时,必须初始化其所有@Prop变量。
## @Link ## @Link
@Link装饰的变量可以和父组件的@State变量建立双向数据绑定: @Link装饰的变量可以和父组件的@State变量建立双向数据绑定:
- 支持多种类型:@Link变量的值与@State变量的类型相同,即class、number、string、boolean或这些类型的数组; - 支持多种类型:@Link支持的数据类型与@State相同,即class、number、string、boolean或这些类型的数组;
- 私有:仅组件内访问; - 私有:仅支持组件内访问;
- 单个数据源:初始化@Link变量的父组件的变量必须是@State变量; - 单个数据源:父组件中用于初始化子组件@Link变量的必须是@State变量;
- 双向通信:子组件对@Link变量的更改将同步修改父组件的@State变量; - 双向通信:子组件对@Link变量的更改将同步修改父组件的@State变量;
- 创建自定义组件时需要将变量的引用传递给@Link变量,在创建组件的新实例时,必须使用命名参数初始化所有@Link变量。@Link变量可以使用@State变量或@Link变量的引用进行初始化,@State变量可以通过`'$'`操作符创建引用。 - 创建自定义组件时需要将变量的引用传递给@Link变量,在创建组件的新实例时,必须使用命名参数初始化所有@Link变量。@Link变量可以使用@State变量或@Link变量的引用进行初始化,@State变量可以通过`'$'`操作符创建引用。
> 说明: @Link变量不能在组件内部进行初始化 > **说明:** @Link变量不能在组件内部进行初始化。
**简单类型示例:** **简单类型示例:**
@Link语义是从`'$'`操作符引出,即`$isPlaying``this.isPlaying`内部状态的双向数据绑定。当单击PlayButton时,PlayButton的Image组件和Text组件将同时进行刷新。 @Link语义是从`'$'`操作符引出,即`$isPlaying``this.isPlaying`内部状态的双向数据绑定。当单击子组件PlayButton中的按钮时,@Link变量更改,PlayButton与父组件中的Text和Button将同时进行刷新,同样地,当点击父组件中的Button修改`this.isPlaying`时,子组件PlayButton与父组件中的Text和Button也将同时刷新。
```ts ```ts
// xxx.ets // xxx.ets
@Entry @Entry
@Component @Component
struct Player { struct Player {
@State isPlaying: boolean = false @State isPlaying: boolean = false
build() {
Column() { build() {
PlayButton({buttonPlaying: $isPlaying}) Column() {
Text(`Player is ${this.isPlaying? '':'not'} playing`) PlayButton({ buttonPlaying: $isPlaying })
} Text(`Player is ${this.isPlaying ? '' : 'not'} playing`).fontSize(18)
Button('Parent:' + this.isPlaying)
.margin(15)
.onClick(() => {
this.isPlaying = !this.isPlaying
})
} }
}
} }
@Component @Component
struct PlayButton { struct PlayButton {
@Link buttonPlaying: boolean @Link buttonPlaying: boolean
build() {
Column() { build() {
Button() { Column() {
Image(this.buttonPlaying? 'play.png' : 'pause.png') Button(this.buttonPlaying ? 'pause' : 'play')
}.onClick(() => { .margin(20)
this.buttonPlaying = !this.buttonPlaying .onClick(() => {
}) this.buttonPlaying = !this.buttonPlaying
} })
} }
}
} }
``` ```
...@@ -194,82 +205,97 @@ struct PlayButton { ...@@ -194,82 +205,97 @@ struct PlayButton {
@Entry @Entry
@Component @Component
struct Parent { struct Parent {
@State arr: number[] = [1, 2, 3] @State arr: number[] = [1, 2, 3]
build() {
Column() { build() {
Child({items: $arr}) Column() {
ForEach(this.arr, Child({ items: $arr })
item => Text(`${item}`), Button('Parent Button: splice')
item => item.toString()) .margin(10)
} .onClick(() => {
this.arr.splice(0, 1, 60)
})
ForEach(this.arr, item => {
Text(item.toString()).fontSize(18).margin(10)
}, item => item.toString())
} }
}
} }
@Component @Component
struct Child { struct Child {
@Link items: number[] @Link items: number[]
build() {
Column() { build() {
Button() { Column() {
Text('Button1: push') Button('Child Button1: push')
}.onClick(() => { .margin(15)
this.items.push(100) .onClick(() => {
}) this.items.push(100)
Button() { })
Text('Button2: replace whole item') Button('Child Button2: replace whole item')
}.onClick(() => { .margin(15)
this.items = [100, 200, 300] .onClick(() => {
}) this.items = [100, 200, 300]
} })
} }
}
} }
``` ```
## @Link、@State和@Prop结合使用示例 **@Link、@State和@Prop结合使用示例:**
下面示例中,ParentView包含ChildA和ChildB两个子组件,ParentView的状态变量counter分别初始化ChildA和ChildB 下面示例中,ParentView包含ChildA和ChildB两个子组件,ParentView的状态变量counter分别用于初始化ChildA的@Prop变量和ChildB的@Link变量
- ChildB使用@Link建立双向状态绑定。当ChildB修改counterRef状态变量值时,该更改将同步到ParentView和ChildA共享; - ChildB使用@Link建立双向数据绑定,当ChildB修改counterRef状态变量值时,该更改将同步到ParentView和ChildA共享;
- ChildA使用@Prop建立从ParentView到自身的单向状态绑定。当ChildA修改状态时,ChildA将重新渲染,但该更改不会传达给ParentView和ChildB。 - ChildA使用@Prop建立从ParentView到自身的单向数据绑定,当ChildA修改counterVal状态变量值时,ChildA将重新渲染,但该更改不会传达给ParentView和ChildB。
```ts ```ts
// xxx.ets // xxx.ets
@Entry @Entry
@Component @Component
struct ParentView { struct ParentView {
@State counter: number = 0 @State counter: number = 0
build() {
Column() { build() {
ChildA({counterVal: this.counter}) // pass by value Column() {
ChildB({counterRef: $counter}) // $ creates a Reference that can be bound to counterRef ChildA({ counterVal: this.counter })
} ChildB({ counterRef: $counter })
} }
}
} }
@Component @Component
struct ChildA { struct ChildA {
@Prop counterVal: number @Prop counterVal: number
build() {
Button() { build() {
Text(`ChildA: (${this.counterVal}) + 1`) Button(`ChildA: (${this.counterVal}) + 1`)
}.onClick(() => {this.counterVal+= 1}) .margin(15)
} .onClick(() => {
this.counterVal += 1
})
}
} }
@Component @Component
struct ChildB { struct ChildB {
@Link counterRef: number @Link counterRef: number
build() {
Button() { build() {
Text(`ChildB: (${this.counterRef}) + 1`) Button(`ChildB: (${this.counterRef}) + 1`)
}.onClick(() => {this.counterRef+= 1}) .margin(15)
} .onClick(() => {
this.counterRef += 1
})
}
} }
``` ```
## Observed和ObjectLink数据管理 ## @Observed和ObjectLink数据管理
当开发者需要在子组件中针对父组件的一个变量(parent_a)设置双向同步时,开发者可以在父组件中使用\@State装饰变量(parent_a),并在子组件中使用@Link装饰相应的变量(child_a)。这样的话,不仅可以实现父组件与单个子组件之间的数据同步,也可以实现父组件与多个子组件之间的数据同步。如下图所示,可以看到,父子组件针对ClassA类型的变量设置了双向同步,那么当子组件1中变量的属性c的值变化时,会通知父组件同步变化,而当父组件中属性c的值变化时,会通知所有子组件同步变化。 当开发者需要在子组件中针对父组件的一个变量(parent_a)设置双向同步时,开发者可以在父组件中使用@State装饰变量(parent_a),并在子组件中使用@Link装饰对应的变量(child_a)。这样不仅可以实现父组件与单个子组件之间的数据同步,也可以实现父组件与多个子组件之间的数据同步。如下图所示,可以看到,父子组件针对ClassA类型的变量设置了双向同步,那么当子组件1中变量对应的属性c的值变化时,会通知父组件同步变化,而当父组件中属性c的值变化时,会通知所有子组件同步变化。
![zh-cn_image_0000001251090821](figures/zh-cn_image_0000001251090821.png) ![zh-cn_image_0000001251090821](figures/zh-cn_image_0000001251090821.png)
...@@ -279,17 +305,17 @@ struct ChildB { ...@@ -279,17 +305,17 @@ struct ChildB {
### 设置要求 ### 设置要求
- @Observed 用于类,@ObjectLink 用于变量。 - @Observed用于类,@ObjectLink用于变量。
- @ObjectLink装饰的变量类型必须为类(class type)。 - @ObjectLink装饰的变量类型必须为类(class type)。
- 类要被\@Observed装饰器所装饰。 - 类要被@Observed装饰器所装饰。
- 不支持简单类型参数,可以使用@Prop进行单向同步。 - 不支持简单类型参数,可以使用@Prop进行单向同步。
- @ObjectLink装饰的变量是不可变的(immutable) - @ObjectLink装饰的变量是不可变的。
- 属性的改动是被允许的,当改动发生时,如果同一个对象被多个@ObjectLink变量所引用,那么所有拥有这些变量的自定义组件都会被通知重新渲染。 - 属性的改动是被允许的,当改动发生时,如果同一个对象被多个@ObjectLink变量所引用,那么所有拥有这些变量的自定义组件都会被通知进行重新渲染。
- @ObjectLink装饰的变量不可设置默认值。 - @ObjectLink装饰的变量不可设置默认值。
- 必须让父组件中有一个由@State、@Link、@StorageLink、@Provide或@Consume所装饰变量参与的TS表达式进行初始化。 - 必须让父组件中有一个由@State、@Link、@StorageLink、@Provide或@Consume装饰的变量所参与的TS表达式进行初始化。
- @ObjectLink装饰的变量是私有变量,只能在组件内访问。 - @ObjectLink装饰的变量是私有变量,只能在组件内访问。
...@@ -299,30 +325,31 @@ struct ChildB { ...@@ -299,30 +325,31 @@ struct ChildB {
```ts ```ts
// xxx.ets // xxx.ets
// 父组件ViewB中的类对象ClassA与子组件ViewA保持数据同步时,可以使用@ObjectLink和@Observed,绑定该数据对象的父组件和其他子组件同步更新 // 父组件ViewB中的类对象ClassA与子组件ViewA保持数据同步时,可以使用@ObjectLink和@Observed,绑定该数据对象的父组件和其他子组件同步更新
var nextID: number = 0
var nextID: number = 0;
@Observed @Observed
class ClassA { class ClassA {
public name : string; public name: string
public c: number; public c: number
public id : number; public id: number
constructor(c: number, name: string = 'OK') { constructor(c: number, name: string = 'OK') {
this.name = name; this.name = name
this.c = c; this.c = c
this.id = nextID++; this.id = nextID++
} }
} }
@Component @Component
struct ViewA { struct ViewA {
label : string = "ViewA1"; label: string = 'ViewA1'
@ObjectLink a: ClassA; @ObjectLink a: ClassA
build() { build() {
Row() { Row() {
Button(`ViewA [${this.label}] this.a.c= ${this.a.c} +1`) Button(`ViewA [${this.label}] this.a.c= ${this.a.c} +1`)
.onClick(() => { .onClick(() => {
this.a.c += 1; this.a.c += 1
}) })
}.margin({ top: 10 }) }.margin({ top: 10 })
} }
...@@ -331,59 +358,57 @@ struct ViewA { ...@@ -331,59 +358,57 @@ struct ViewA {
@Entry @Entry
@Component @Component
struct ViewB { struct ViewB {
@State arrA : ClassA[] = [ new ClassA(0), new ClassA(0) ]; @State arrA: ClassA[] = [new ClassA(0), new ClassA(0)]
build() { build() {
Column() { Column() {
ForEach (this.arrA, (item) => { ForEach(this.arrA, (item) => {
ViewA({label: `#${item.id}`, a: item}) ViewA({ label: `#${item.id}`, a: item })
}, }, (item) => item.id.toString())
(item) => item.id.toString() ViewA({ label: `this.arrA[first]`, a: this.arrA[0] })
) ViewA({ label: `this.arrA[last]`, a: this.arrA[this.arrA.length - 1] })
ViewA({label: `ViewA this.arrA[first]`, a: this.arrA[0]})
ViewA({label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1]})
Button(`ViewB: reset array`) Button(`ViewB: reset array`)
.margin({ top: 10 }) .margin({ top: 10 })
.onClick(() => { .onClick(() => {
this.arrA = [ new ClassA(0), new ClassA(0) ]; this.arrA = [new ClassA(0), new ClassA(0)]
}) })
Button(`ViewB: push`) Button(`ViewB: push`)
.margin({ top: 10 }) .margin({ top: 10 })
.onClick(() => { .onClick(() => {
this.arrA.push(new ClassA(0)) this.arrA.push(new ClassA(0))
}) })
Button(`ViewB: shift`) Button(`ViewB: shift`)
.margin({ top: 10 }) .margin({ top: 10 })
.onClick(() => { .onClick(() => {
this.arrA.shift() this.arrA.shift()
}) })
} }.width('100%')
} }
} }
``` ```
![Observed](figures/Observed.gif)
## @Consume和@Provide ## @Consume和@Provide
Provide作为数据的提供方,可以更新其子孙节点的数据,并触发页面渲染。Consume在感知到Provide数据的更新后,会触发当前view的重新渲染。 @Provide作为数据的提供方,可以更新其子孙节点的数据,并触发页面渲染。@Consume在感知到@Provide数据的更新后,会触发当前自定义组件的重新渲染。
> **说明:** 使用@Provide 和@Consume时避免循环引用导致死循环。 > **说明:** 使用@Provide和@Consume时应避免循环引用导致死循环。
表1 @Provide ### @Provide
| 名称 | 说明 | | 名称 | 说明 |
| -------------- | ------------------------------------------------------------ | | -------------- | ------------------------------------------------------------ |
| 装饰器参数 | 是一个string类型的常量,用于给装饰的变量起别名。如果规定别名,则提供对应别名的数据更新。如果没有,则使用变量名作为别名。推荐使用@Provide("alias")这种形式。 | | 装饰器参数 | 是一个string类型的常量,用于给装饰的变量起别名。如果规定别名,则提供对应别名的数据更新。如果没有,则使用变量名作为别名。推荐使用@Provide('alias')这种形式。 |
| 同步机制 | @Provide的变量类似@State,可以修改对应变量进行页面重新渲染。也可以修改@Consume装饰的变量,反向修改@State变量。 | | 同步机制 | @Provide的变量类似@State,可以修改对应变量进行页面重新渲染。也可以修改@Consume装饰的变量,反向修改@State变量。 |
| 初始值 | 必须制定初始值。 | | 初始值 | 必须设置初始值。 |
| 页面重渲染场景 | 触发页面渲染的修改: <br/>- 基础类型(boolean,string,number)变量的改变; <br/>- @Observed class类型变量及其属性的修改; <br/>- 添加,删除,更新数组中的元素。 | | 页面重渲染场景 | 触发页面渲染的修改: <br/>- 基础类型(boolean,string,number)变量的改变; <br/>- @Observed class类型变量及其属性的修改; <br/>- 添加,删除,更新数组中的元素。 |
表2 @Consume ### @Consume
| 类型 | 说明 | | 类型 | 说明 |
| ------ | ---------------- | | ------ | ---------------- |
| 初始值 | 不可设置默认值。 | | 初始值 | 不可设置默认初始值。 |
### 示例 ### 示例
...@@ -392,18 +417,16 @@ Provide作为数据的提供方,可以更新其子孙节点的数据,并触 ...@@ -392,18 +417,16 @@ Provide作为数据的提供方,可以更新其子孙节点的数据,并触
@Entry @Entry
@Component @Component
struct CompA { struct CompA {
@Provide("reviewVote") reviewVotes : number = 0; @Provide("reviewVote") reviewVotes: number = 0;
build() { build() {
Column() { Column() {
CompB() CompB()
Button() { Button(`CompA: ${this.reviewVotes}`)
Text(`${this.reviewVotes}`) .margin(10)
.fontSize(30) .onClick(() => {
} this.reviewVotes += 1;
.onClick(() => { })
this.reviewVotes += 1;
})
} }
} }
} }
...@@ -419,17 +442,16 @@ struct CompB { ...@@ -419,17 +442,16 @@ struct CompB {
@Component @Component
struct CompC { struct CompC {
@Consume("reviewVote") reviewVotes : number; @Consume("reviewVote") reviewVotes: number
build() { build() {
Column() { Column() {
Button() { Button(`CompC: ${this.reviewVotes}`)
Text(`${this.reviewVotes}`) .margin(10)
.fontSize(30) .onClick(() => {
} this.reviewVotes += 1
.onClick(() => { })
this.reviewVotes += 1; }.width('100%')
})
}.margin({ left:10, top: 10 })
} }
} }
``` ```
...@@ -438,41 +460,53 @@ struct CompC { ...@@ -438,41 +460,53 @@ struct CompC {
@Watch用于监听状态变量的变化,语法结构为: @Watch用于监听状态变量的变化,语法结构为:
``` ```ts
@State @Watch("onChanged") count : number = 0 @State @Watch("onChanged") count : number = 0
``` ```
如上给状态变量增加一个@Watch装饰器,通过@Watch注册一个回调方法onChanged, 当状态变量count被改变时, 触发onChanged回调。 如上所示,给状态变量增加一个@Watch装饰器,通过@Watch注册一个回调方法onChanged, 当状态变量count被改变时, 触发onChanged回调。
装饰器@State、@Prop、@Link、@ObjectLink、@Provide、@Consume、@StorageProp以及@StorageLink装饰的变量可以监听其变化。 装饰器@State、@Prop、@Link、@ObjectLink、@Provide、@Consume、@StorageProp以及@StorageLink所装饰的变量均可以通过@Watch监听其变化。
``` ```ts
// xxx.ets // xxx.ets
@Entry @Entry
@Component @Component
struct CompA { struct CompA {
@State @Watch("onBasketUpdated") shopBasket : Array<number> = [ 7, 12, 47, 3 ]; @State @Watch('onBasketUpdated') shopBasket: Array<number> = [7, 12, 47, 3]
@State totalPurchase : number = 0; @State totalPurchase: number = 0
@State addPurchase: number = 0
updateTotal() : number {
let sum = 0;
this.shopBasket.forEach((i) => { sum += i; });
// 计算新的购物篮总价值,如果超过100RMB,则适用折扣
this.totalPurchase = (sum < 100) ? sum : 0.9 * sum;
return this.totalPurchase;
}
// @Watch cb aboutToAppear() {
onBasketUpdated(propName: string) : void { this.updateTotal()
this.updateTotal(); }
}
updateTotal(): number {
let sum = 0;
this.shopBasket.forEach((i) => {
sum += i
})
// 计算新的购物篮总价值,如果超过100,则适用折扣
this.totalPurchase = (sum < 100) ? sum : 0.9 * sum
return this.totalPurchase
}
build() { // shopBasket更改时触发该方法
Column() { onBasketUpdated(propName: string): void {
Button("add to basket").onClick(() => { this.shopBasket.push(Math.round(100 * Math.random())) }) this.updateTotal()
Text(`${this.totalPurchase}`) }
.fontSize(30)
} build() {
Column() {
Button('add to basket ' + this.addPurchase)
.margin(15)
.onClick(() => {
this.addPurchase = Math.round(100 * Math.random())
this.shopBasket.push(this.addPurchase)
})
Text(`${this.totalPurchase}`)
.fontSize(30)
} }
}
} }
``` ```
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册