HarmonyOs 4 (三) ArkTS语言
目录
- 一 认识ArkTs语言
- 1.1 ArkTs
- 1.2 基本结构
- 二 基本语法
- 2.1 声明式UI
- 2.1.1 创建组件
- 2.1.1.1 无参数
- 2.1.1.2 有参数
- 2.1.1.3 组件样式
- 2.1.1.4 组件方法
- 2.1.1.5 组件嵌套
- 2.1.2 自定义组件
- 2.1.2.1 基本结构
- 2.1.2.2 成员函数/变量
- 2.1.2.3 自定义组件的参数规定
- 2.1.2.4 Build函数
- 2.1.2.5 自定义组件通用样式
- 2.1.3 组件生命周期
- 2.1.4 @Builder装饰器:自定义构建函数
- 2.1.4.1 自定义组件内自定义构建函数
- 2.1.4.2 全局自定义构建函数
- 2.1.4.3 参数传递规则
- 按引用传递参数
- 按值传递参数
- 2.1.5 @BuilderParam装饰器
- 2.1.6 @Styles装饰器
- 2.1.7 @Extend装饰器
- 2.1.8 多态样式
- 2.2 状态管理
- 2.2.1 基本概念
- 2.2.2 状态管管理
- 2.2.3 基本装饰器
- 2.2.3 管理组件状态
- 2.2.3.1 @State装饰器:组件内状态
- 2.2.3.2 @Prop装饰器:父子单向同步
- 2.2.3.3 @Link装饰器:父子双向同步
- 2.2.3.4 @Provide装饰器和@Consume装饰器:同级组件通信
- 2.2.3.5 @Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
- 2.2.4 应用组件状态
- 2.2.4.1 LocalStorage:页面级UI状态存储
- 2.2.4.2 AppStorage:应用全局的UI状态存储
- 2.2.4.3 PersistentStorage:持久化存储UI状态
- 2.2.4.4 Environment:设备环境查询
- 2.2.5 其他状态
- 2.2.5.1 @Watch装饰器:状态变量更改通知
- 2.2.5.2 $$语法:内置组件双向同步
- 2.3 渲染控制
- 2.3.1 if/else:条件渲染
- 2.3.2 ForEach:循环渲染
- 2.3.3 LazyForEach:数据懒加载
- 💌 所属专栏:【HarmonyOs】
- 😀 作 者:长安不及十里
- 💻 工作:目前从事电力行业开发
- 🌈 目标:全栈开发
- 🚀 个人简介:一个正在努力学技术的Java工程师,专注基础和实战分享 ,欢迎咨询!
- 📱:测试设备:荣耀30pro+HarmonyOs4
参考资料:
文档中心
一 认识ArkTs语言
1.1 ArkTs
ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript(简称TS)生态基础上做了进一步扩展,继承了TS的所有特性,是TS的超集。因此,在学习ArkTS语言之前,建议开发者具备TS语言开发能力。
当前,ArkTS在TS的基础上主要扩展了如下能力:
- 基本语法:ArkTS定义了声明式UI描述、自定义组件和动态扩展UI元素的能力,再配合ArkUI开发框架中的系统组件及其相关的事件方法、属性方法等共同构成了UI开发的主体。
- 状态管理:ArkTS提供了多维度的状态管理机制。在UI开发框架中,与UI相关联的数据可以在组件内使用,也可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,还可以在应用全局范围内传递或跨设备传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活的利用这些能力来实现数据和UI的联动。
- 渲染控制:ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。数据懒加载从数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。
未来,ArkTS会结合应用开发/运行的需求持续演进,逐步提供并行和并发能力增强、系统类型增强、分布式开发范式等更多特性。
1.2 基本结构
- 装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义,如上述示例中@Entry、@Component和@State都是装饰器,@Component表示自定义组件,@Entry表示该自定义组件为入口组件,@State表示组件中的状态变量,状态变量变化会触发UI刷新。
- UI描述:以声明式的方式来描述UI的结构,例如build()方法中的代码块。
- 自定义组件:可复用的UI单元,可组合其他组件,如上述被@Component装饰的struct Hello。
- 系统组件:ArkUI框架中默认内置的基础和容器组件,可直接被开发者调用,比如示例中的Column、Text、Divider、Button。
- 属性方法:组件可以通过链式调用配置多项属性,如fontSize()、width()、height()、backgroundColor()等。
- 事件方法:组件可以通过链式调用设置多个事件的响应逻辑,如跟随在Button后面的onClick()。
- 系统组件、属性方法、事件方法具体使用可参考基于ArkTS的声明式开发范式。
除此之外,ArkTS扩展了多种语法范式来使开发更加便捷: - @Builder/@BuilderParam:特殊的封装UI描述的方法,细粒度的封装和复用UI描述。
- @Extend/@Style:扩展内置组件和封装属性样式,更灵活地组合内置组件。
- stateStyles:多态样式,可以依据组件的内部状态的不同,设置不同样式。
二 基本语法
2.1 声明式UI
ArkTS以声明方式组合和扩展组件来描述应用程序的UI,同时还提供了基本的属性、事件和子组件配置方法,帮助开发者实现应用交互逻辑。
2.1.1 创建组件
- 根据组件构造方法的不同,创建组件包含有参数和无参数两种方式。
- 创建组件时不需要new运算符。
- 最好有Ts语法基础。
2.1.1.1 无参数
注意:Column、Row、Stack、Grid、List等组件都是容器组件,我们的顶层需要在容器组件中
如果组件的接口定义没有包含必选构造参数,则组件后面的“()”不需要配置任何内容。例如,Divider组件不包含构造参数
2.1.1.2 有参数
- 必填参数,比如Image
- 非必选参数,比如Text组件的非必选参数content
Column() {Text('无参数组件Column')Divider()Text('item 2')Divider()Text($r('app.string.app_name'))Divider()Text()
}
- 变量或表达式也可以用于参数赋值,其中表达式返回的结果类型必须满足参数类型要求。例如,设置变量或表达式来构造Image和Text组件的参数。
Image(this.imagePath)
Image('https://' + this.imageUrl)
Text(`count: ${this.count}`)
2.1.1.3 组件样式
组件的样式控制,有点类似Java 中的链式编程
@Entry
@Component
struct Index {@State message: string = 'Hello World'@State url: string="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/p9/wallhaven-p97l5e.png?w=2560&h=1440&fmt=webp"build() {Row() {Column() {Text('无参数组件Column')Divider()Text('item 2')Divider()Text($r('app.string.app_name'))Divider()Text()Divider()Image(this.url).alt('error.jpg').width(100).height(100)}}.height('100%')}
}
2.1.1.4 组件方法
事件方法以“.”链式调用的方式配置系统组件支持的事件,建议每个事件方法单独写一行
- 点击按钮,改变颜色
@Entry
@Component
struct Index {@State message: string = 'Hello World'@State url: string="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/p9/wallhaven-p97l5e.png?w=2560&h=1440&fmt=webp"@State backgroundColors:Color=Color.Bluebuild() {Row() {Column() {Text('无参数组件Column')Divider()Text('item 2')Divider()Text($r('app.string.app_name'))Divider()Text()Divider()Image(this.url).alt('error.jpg').width(100).height(100)Divider()Button("点击事件").backgroundColor(this.backgroundColors).margin(10).width(100).height(50).onClick(()=>{console.log("你点击了这个按钮哦")this.backgroundColors=Color.Green}).onHover(e=>{// 改变颜色console.log("你点击了这个按钮哦1111")})}}.height('100%')}
}
2.1.1.5 组件嵌套
2.1.2 自定义组件
在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。在进行 UI 界面开发时,通常不是简单的将系统组件进行组合使用,而是需要考虑代码可复用性、业务逻辑与UI分离,后续版本演进等因素。因此,将UI和部分业务逻辑封装成自定义组件是不可或缺的能力。
自定义组件具有以下特点:
- 可组合:允许开发者组合使用系统组件、及其属性和方法。
- 可重用:自定义组件可以被其他组件重用,并作为不同的实例在不同的父组件或容器中使用。
- 数据驱动UI更新:通过状态变量的改变,来驱动UI的刷新。
##
@Entry
@Component
struct Index {@State message: string = 'Hello World'@State url: string="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/p9/wallhaven-p97l5e.png?w=2560&h=1440&fmt=webp"@State backgroundColors:Color=Color.Bluebuild() {Row() {Column(){ImageComponent()}.width(200)Column(){ImageComponent()}.width(200)}.height('100%')}
}@Component
struct ImageComponent {@State url: string = 'https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/p9/wallhaven-p97l5e.png?w=2560&h=1440&fmt=webp';build() {Row() {Image(this.url).width(200).height(200)}}
}
- 传参数({参数})
2.1.2.1 基本结构
- struct:自定义组件基于struct实现,struct + 自定义组件名 + {…}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。说明自定义组件名、类名、函数名不能和系统组件名相同。
@Componentstruct MyComponent {}
- @Component:@Component装饰器仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的能力,需要实现build方法描述UI,一个struct只能被一个@Component装饰。说明从API version 9开始,该装饰器支持在ArkTS卡片中使用。
- build()函数:build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。
@Componentstruct MyComponent {build() {}}
- @Entry:@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件。@Entry可以接受一个可选的LocalStorage的参数。说明从API version 9开始,该装饰器支持在ArkTS卡片中使用。
@Entry@Componentstruct MyComponent {}
2.1.2.2 成员函数/变量
自定义组件除了必须要实现build()函数外,还可以实现其他成员函数,成员函数具有以下约束:
- 不支持静态函数。
- 成员函数的访问是私有的。
自定义组件可以包含成员变量,成员变量具有以下约束:
- 不支持静态成员变量。
- 所有成员变量都是私有的,变量的访问规则与成员函数的访问规则相同。
- 自定义组件的成员变量本地初始化有些是可选的,有些是必选的。具体是否需要本地初始化,是否需要从父组件通过参数传递初始化子组件的成员变量
2.1.2.3 自定义组件的参数规定
我们已经了解到,可以在build方法或者@Builder装饰的函数里创建自定义组件,在创建自定义组件的过程中,根据装饰器的规则来初始化自定义组件的参数。
@Entry
@Component
struct Index {build() {Row() {Column(){ImageComponent()}.width(200)Column(){ImageComponent({url:"https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/ex/wallhaven-exwgw8.png?w=2560&h=1440&fmt=webp"})}.width(200)}.height('100%')}
}@Component
struct ImageComponent {@State url: string = 'https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/p9/wallhaven-p97l5e.png?w=2560&h=1440&fmt=webp';build() {Row() {Image(this.url).width(200).height(200)}}
}
2.1.2.4 Build函数
所有声明在build()函数的语言,我们统称为UI描述,UI描述需要遵循以下规则:
- @Entry装饰的自定义组件,其build()函数下的根节点唯一且必要,且必须为容器组件,其中ForEach禁止作为根节点。
- @Component装饰的自定义组件,其build()函数下的根节点唯一且必要,可以为非容器组件,其中ForEach禁止作为根节点。
@Entry
@Component
struct MyComponent {build() {// 根节点唯一且必要,必须为容器组件Row() {ChildComponent() }}
}@Component
struct ChildComponent {build() {// 根节点唯一且必要,可为非容器组件Image('test.jpg')}
}
- 不允许声明本地变量
build() {// 反例:不允许声明本地变量let a: number = 1;
}
- 不允许在UI描述里直接使用console.info,但允许在方法或者函数里使用,反例如下。
build() {// 反例:不允许console.infoconsole.info('print debug log');
}
- 不允许创建本地的作用域,反例如下
build() {// 反例:不允许本地作用域{...}
}
- 不允许调用没有用@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())}}
}
- 不允许switch语法,如果需要使用条件判断,请使用if。
build() {Column() {// 反例:不允许使用switch语法switch (expression) {case 1:Text('...')break;case 2:Image('...')break;default:Text('...')break;}}
}
2.1.2.5 自定义组件通用样式
ArkUI给自定义组件设置样式时,相当于给MyComponent2套了一个不可见的容器组件,而这些样式是设置在容器组件上的,而非直接设置给MyComponent2的Button组件。通过渲染结果我们可以很清楚的看到,背景颜色红色并没有直接生效在Button上,而是生效在Button所处的开发者不可见的容器组件上。
2.1.3 组件生命周期
在开始之前,我们先明确自定义组件和页面的关系:
- 自定义组件:@Component装饰的UI单元,可以组合多个系统组件实现UI的复用。
- 页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。
页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:
- onPageShow:页面每次显示时触发。
- onPageHide:页面每次隐藏时触发一次。
- onBackPress:当用户点击返回按钮时触发。
组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:
- aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。
- aboutToDisappear:在自定义组件即将析构销毁时执行。
生命周期流程如下图所示,下图展示的是被@Entry装饰的组件(首页)生命周期。
根据上面的流程图,我们从自定义组件的初始创建、重新渲染和删除来详细解释。
// Index.ets
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 aboutToAppearif (this.showChild) {Child()}// this.showChild为false,删除Child子组件,执行Child aboutToDisappearButton('create or delete Child').onClick(() => {this.showChild = false;})// push到Page2页面,执行onPageHideButton('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';})}
}
- 应用冷启动的初始化流程为:MyComponent aboutToAppear --> MyComponent build --> Child aboutToAppear --> Child build --> Child build执行完毕 --> MyComponent build执行完毕 --> Index onPageShow。
- 点击“delete Child”,if绑定的this.showChild变成false,删除Child组件,会执行Child aboutToDisappear方法。
- 点击“push to next page”,调用router.pushUrl接口,跳转到另外一个页面,当前Index页面隐藏,执行页面生命周期Index onPageHide。此处调用的是router.pushUrl接口,Index页面被隐藏,并没有销毁,所以只调用onPageHide。跳转到新页面后,执行初始化新页面的生命周期的流程。
- 如果调用的是router.replaceUrl,则当前Index页面被销毁,执行的生命周期流程将变为:Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。上文已经提到,组件的销毁是从组件树上直接摘下子树,所以先调用父组件的aboutToDisappear,再调用子组件的aboutToDisappear,然后执行初始化新页面的生命周期流程。
- 点击返回按钮,触发页面生命周期Index onBackPress,且触发返回一个页面后会导致当前Index页面被销毁。
- 最小化应用或者应用进入后台,触发Index onPageHide。当前Index页面没有被销毁,所以并不会执行组件的aboutToDisappear。应用回到前台,执行Index onPageShow。
- 退出应用,执行Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。
2.1.4 @Builder装饰器:自定义构建函数
- 该自定义组件内部UI结构固定,仅与使用方进行数据传递。ArkUI还提供了一种更轻量的UI元素复用机制@Builder,@Builder所装饰的函数遵循build()函数语法规则,开发者可以将重复使用的UI元素抽象成一个方法,在build方法里调用。
- 为了简化语言,我们将@Builder装饰的函数也称为“自定义构建函数”。
说明
从API version 9开始,该装饰器支持在ArkTS卡片中使用。
2.1.4.1 自定义组件内自定义构建函数
@Component
struct ImageComponent {@Builder MyBuilderFunction(){Row() {Image(this.url).width(200).height(200)}}@State url: string = 'https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/p9/wallhaven-p97l5e.png?w=2560&h=1440&fmt=webp';build() {this.MyBuilderFunction()}
}
定义的语法:
@Builder MyBuilderFunction(){ ... }
使用方法:
this.MyBuilderFunction(){ ... }
- 允许在自定义组件内定义一个或多个@Builder方法,该方法被认为是该组件的私有、特殊类型的成员函数。
- 自定义构建函数可以在所属组件的build方法和其他自定义构建函数中调用,但不允许在组件外调用。
- 在自定义函数体中,this指代当前所属组件,组件的状态变量可以在自定义构建函数内访问。建议通过this访问自定义组件的状态变量而不是参数传递。
2.1.4.2 全局自定义构建函数
@Builder function MyGlobalBuilderImageComponent(){Row() {Image('https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/p9/wallhaven-p97l5e.png?w=2560&h=1440&fmt=webp').width(200).height(200)}
}
定义的语法:
@Builder function MyGlobalBuilderFunction(){ ... }
使用方法:
MyGlobalBuilderFunction()
- 全局的自定义构建函数可以被整个应用获取,不允许使用this和bind方法。
- 如果不涉及组件状态变化,建议使用全局的自定义构建方法。
2.1.4.3 参数传递规则
自定义构建函数的参数传递有按值传递和按引用传递两种,均需遵守以下规则:
- 参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。
- 在自定义构建函数内部,不允许改变参数值。如果需要改变参数值,且同步回调用点,建议使用@Link。
- @Builder内UI语法遵循UI语法规则。
按引用传递参数
按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起@Builder方法内的UI刷新。ArkUI提供$$作为按引用传递参数的范式。
@Builder function MyGlobalBuilderImageComponent($$ : { url: string } ){Row() {Image(`${$$.url}`).width(200).height(200)}
}
@Entry
@Component
struct Index {@State urls: string="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/9d/wallhaven-9d9111.jpg?w=2560&h=1440&fmt=webp"build() {Row() {Column(){MyGlobalBuilderImageComponent({url:this.urls })Divider()Button("改变图片").onClick(()=>{this.urls="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/85/wallhaven-8596q2.jpg?w=2560&h=1440&fmt=webp"}).width(200).width(100)}.width(200)Column(){ImageComponent()Divider()Button("改变图片").onClick(()=>{}).width(200).width(100)}.width(200)}.height('100%')}
}
按值传递参数
调用@Builder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起@Builder方法内的UI刷新。所以当使用状态变量的时候,推荐使用按引用传递。
@Entry
@Component
struct Index {urls: string="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/9d/wallhaven-9d9111.jpg?w=2560&h=1440&fmt=webp"build() {Row() {Column(){MyGlobalBuilderImageComponent({url:this.urls })Divider()Button("改变图片").onClick(()=>{this.urls="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/85/wallhaven-8596q2.jpg?w=2560&h=1440&fmt=webp"}).width(200).width(100)}.width(200)Column(){ImageComponent()Divider()Button("改变图片").onClick(()=>{}).width(200).width(100)}.width(200)}.height('100%')}
}@Component
struct ImageComponent {@State url:string="https://dogefs.s3.ladydaily.com/~/source/wallhaven/full/9d/wallhaven-9d89zw.jpg?w=2560&h=1440&fmt=webp"@Builder MyBuilderFunction($$ : { url: string } ){Row() {Image(`${$$.url}`).width(200).height(200)}}build() {this.MyBuilderFunction({ url: this.url})}
}@Builder function MyGlobalBuilderImageComponent($$ : { url: string } ){Row() {Image(`${$$.url}`).width(200).height(200)}
}
点击改变图片,没有变化
2.1.5 @BuilderParam装饰器
- 当开发者创建了自定义组件,并想对该组件添加特定功能时,例如在自定义组件中添加一个点击跳转操作。
- 若直接在组件内嵌入事件方法,将会导致所有引入该自定义组件的地方均增加了该功能。为解决此问题,ArkUI引入了@BuilderParam装饰器,@BuilderParam用来装饰指向@Builder方法的变量,开发者可在初始化自定义组件时对此属性进行赋值,为自定义组件增加特定的功能。
- 该装饰器用于声明任意UI描述的一个元素,类似slot占位符。
@Entry
@Component
struct Index {@Builder component1Builder() {Column(){Row() {Image("https://www4.bing.com//th?id=OHR.ThreeElephants_ZH-CN8708711085_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp").width(200).height(200)}Divider()}.width(200)}build() {Row() {ImageComponentSolt0({component0:MyGlobalBuilderImageComponent})ImageComponentSolt01({component1:this.component1Builder})}.height('100%')}
}@Builder function MyGlobalBuilderImageComponent($$ : { url: string } ){Row() {Image(`${$$.url}`).width(200).height(200)}
}@Component
struct ImageComponentSolt0 {// 一个插槽:内容由里面的决定(有参)@BuilderParam component0: ($$ : { url : string}) => void;build() {Column(){this.component0({url:"https://www4.bing.com//th?id=OHR.CameraSquirrel_ZH-CN3580119980_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp"})Divider()}.width(200)}
}@Component
struct ImageComponentSolt01{// 一个插槽:内容由里面的决定(无参)@BuilderParam component1: () => void;build() {Column(){this.component1()Divider()}.width(200)}
}
- @BuilderParam装饰的方法可以是有参数和无参数的两种形式,需与指向的@Builder方法类型匹配。
- @BuilderParam装饰的方法类型需要和@Builder方法类型一致。
- 在自定义组件中使用@BuilderParam装饰的属性时也可通过尾随闭包进行初始化。在初始化自定义组件时,组件后紧跟一个大括号“{}”形成尾随闭包场景。
build() {Row() {ImageComponentSolt0({component0:MyGlobalBuilderImageComponent})// ImageComponentSolt01({component1:this.component1Builder})ImageComponentSolt0(){MyGlobalBuilderImageComponent({url:"https://files.codelife.cc/wallpaper/wallspic/20231122c33j29.jpeg?x-oss-process=image/resize,limit_0,m_fill,w_2560,h_1440/quality,Q_92/format,webp"})}}.height('100%')}
我们来重点理解一下:@BuilderParam最重要的是对@Builder装饰器的引用
2.1.6 @Styles装饰器
如果每个组件的样式都需要单独设置,在开发过程中会出现大量代码在进行重复样式设置,虽然可以复制粘贴,但为了代码简洁性和后续方便维护,我们推出了可以提炼公共样式进行复用的装饰器@Styles。
@Styles装饰器可以将多条样式设置提炼成一个方法,直接在组件声明的位置调用。通过@Styles装饰器可以快速定义并复用自定义样式。用于快速定义并复用自定义样式。
在上面的案例上为他添加统一的样式,统一的高度与宽度
@Entry
@Component
struct Index {@Builder component1Builder() {Column(){Row() {Image("https://www4.bing.com//th?id=OHR.ThreeElephants_ZH-CN8708711085_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp").globalImagesStyle()}Divider()}.width(200)}build() {Row() {ImageComponentSolt0({component0:MyGlobalBuilderImageComponent})ImageComponentSolt01({component1:this.component1Builder})// ImageComponentSolt0(){// MyGlobalBuilderImageComponent({url:"https://files.codelife.cc/wallpaper/wallspic/20231122c33j29.jpeg?x-oss-process=image/resize,limit_0,m_fill,w_2560,h_1440/quality,Q_92/format,webp"})// }}.height('100%')}
}@Builder function MyGlobalBuilderImageComponent($$ : { url: string } ){Row() {Image(`${$$.url}`).globalImagesStyle()}
}@Component
struct ImageComponentSolt0 {// 一个插槽:内容由里面的决定(有参)@BuilderParam component0: ($$ : { url : string}) => void;build() {Column(){this.component0({url:"https://www4.bing.com//th?id=OHR.CameraSquirrel_ZH-CN3580119980_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp"})Divider()}.width(200)}
}@Component
struct ImageComponentSolt01{// 一个插槽:内容由里面的决定(有参)@BuilderParam component1: () => void;build() {Column(){this.component1()Divider()}.width(200)}
}@Styles function globalImagesStyle(){.width(400).height(400).backgroundColor(Color.Black)
}
- 当前@Styles仅支持通用属性和通用事件。
- @Styles方法不支持参数。
- 当让他与@Builder一样分为局部与整体,两个样式冲突时,最近原则
2.1.7 @Extend装饰器
@Extend,用于扩展原生组件样式,简单来说扩展组件
我们在上一个案例上进一步扩展,改变图片的高度
@Extend(Image) function changeHeight(h:number){.height(h)
}
@Entry
@Component
struct Index {@Builder component1Builder() {Column(){Row() {Image("https://www4.bing.com//th?id=OHR.ThreeElephants_ZH-CN8708711085_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp").globalImagesStyle().changeHeight(900)}Divider()}.width(200)}build() {Row() {ImageComponentSolt0({component0:MyGlobalBuilderImageComponent})ImageComponentSolt01({component1:this.component1Builder})// ImageComponentSolt0(){// MyGlobalBuilderImageComponent({url:"https://files.codelife.cc/wallpaper/wallspic/20231122c33j29.jpeg?x-oss-process=image/resize,limit_0,m_fill,w_2560,h_1440/quality,Q_92/format,webp"})// }}.height('100%')}
}@Builder function MyGlobalBuilderImageComponent($$ : { url: string } ){Row() {Image(`${$$.url}`).globalImagesStyle().changeHeight(900)}
}@Component
struct ImageComponentSolt0 {// 一个插槽:内容由里面的决定(有参)@BuilderParam component0: ($$ : { url : string}) => void;build() {Column(){this.component0({url:"https://www4.bing.com//th?id=OHR.CameraSquirrel_ZH-CN3580119980_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp"})Divider()}.width(200)}
}@Component
struct ImageComponentSolt01{// 一个插槽:内容由里面的决定(有参)@BuilderParam component1: () => void;build() {Column(){this.component1()Divider()}.width(200)}
}@Styles function globalImagesStyle(){.width(400).height(400).backgroundColor(Color.Black)
}@Extend(Image) function changeHeight(h:number){.height(h)
}
- @Styles不同,@Extend仅支持定义在全局,不支持在组件内部定义。
- 和@Styles不同,@Extend支持封装指定的组件的私有属性和私有事件和预定义相同组件的@Extend的方法。
- 和@Styles不同,@Extend装饰的方法支持参数,开发者可以在调用时传递参数,调用遵循TS方法传值调用。
- @Extend装饰的方法的参数可以为function,作为Event事件的句柄。
- @Extend的参数可以为状态变量,当状态变量改变时,UI可以正常的被刷新渲染。
2.1.8 多态样式
stateStyles是属性方法,可以根据UI内部状态来设置样式,类似于css伪类,但语法不同。ArkUI提供以下四种状态:
- focused:获焦态。
- normal:正常态。
- pressed:按压态。
- disabled:不可用态。
@Entry
@Component
struct MyComponent {@Styles normalStyle() {.backgroundColor(Color.Gray)}@Styles pressedStyle() {.backgroundColor(Color.Red)}build() {Column() {Text('Text1').fontSize(50).fontColor(Color.White).stateStyles({normal: this.normalStyle,pressed: this.pressedStyle,})}}
}
- stateStyles可以通过this绑定组件内的常规变量和状态变量
@Entry
@Component
struct CompWithInlineStateStyles {@State focusedColor: Color = Color.Red;normalColor: Color = Color.Greenbuild() {Button('clickMe').height(100).width(100).stateStyles({normal: {.backgroundColor(this.normalColor)},focused: {.backgroundColor(this.focusedColor)}}).onClick(() => {this.focusedColor = Color.Pink}).margin('30%')}
}
Button默认获焦显示红色,点击事件触发后,获焦态变为粉色
2.2 状态管理
- 在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。
- 当参数改变时,UI作为返回结果,也将进行对应的改变,这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制。
- 自定义组件拥有变量,变量必须被装饰器装饰才可以成为状态变量,状态变量的改变会引起UI的渲染刷新。如果不使用状态变量,UI只能在初始化时渲染,后续将不会再刷新。 下图展示了State和View(UI)之间的关系。
- View(UI):UI渲染,指将build方法内的UI描述和@Builder装饰的方法内的UI描述映射到界面。
- State:状态,指驱动UI更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染。
2.2.1 基本概念
- 状态变量:被状态装饰器装饰的变量,状态变量值的改变会引起UI的渲染更新。示例:@State num: number = 1,其中,@State是状态装饰器,num是状态变量。
- 常规变量:没有被状态装饰器装饰的变量,通常应用于辅助计算。它的改变永远不会引起UI的刷新。以下示例中increaseBy变量为常规变量。
- 数据源/同步源:状态变量的原始来源,可以同步给不同的状态数据。通常意义为父组件传给子组件的数据。以下示例中数据源为count: 1。
- 命名参数机制:父组件通过指定参数传递给子组件的状态变量,为父子传递同步参数的主要手段。示例:CompA: ({ aProp: this.aProp })。
- 从父组件初始化:父组件使用命名参数机制,将指定参数传递给子组件。子组件初始化的默认值在有父组件传值的情况下,会被覆盖。
- 始化子节点:父组件中状态变量可以传递给子组件,初始化子组件对应的状态变量。示例同上。
- 本地初始化:在变量声明的时候赋值,作为变量的默认值。示例:@State count: number = 0。
2.2.2 状态管管理
ArkUI提供了多种装饰器,通过使用这些装饰器,状态变量不仅可以观察在组件内的改变,还可以在不同组件层级间传递,比如父子组件、跨组件层级,也可以观察全局范围内的变化。根据状态变量的影响范围,将所有的装饰器可以大致分为:
- 管理组件拥有状态的装饰器:组件级别的状态管理,可以观察组件内变化,和不同组件层级的变化,但需要唯一观察同一个组件树上,即同一个页面内。
- 管理应用拥有状态的装饰器:应用级别的状态管理,可以观察不同页面,甚至不同UIAbility的状态变化,是应用内全局的状态管理。
从数据的传递形式和同步类型层面看,装饰器也可分为:
- 只读的单向传递;
- 可变更的双向传递。
2.2.3 基本装饰器
- @State:@State装饰的变量拥有其所属组件的状态,可以作为其子组件单向和双向同步的数据源。当其数值改变时,会引起相关组件的渲染刷新。
- @Prop:@Prop装饰的变量可以和父组件建立单向同步关系,@Prop装饰的变量是可变的,但修改不会同步回父组件。
- @Link:@Link装饰的变量和父组件构建双向同步关系的状态变量,父组件会接受来自@Link装饰的变量的修改的同步,父组件的更新也会同步给@Link装饰的变量。
- @Provide/@Consume:@Provide/@Consume装饰的变量用于跨组件层级(多层组件)同步状态变量,可以不需要通过参数命名机制传递,通过alias(别名)或者属性名绑定。
- @Observed:@Observed装饰class,需要观察多层嵌套场景的class需要被@Observed装饰。单独使用@Observed没有任何作用,需要和@ObjectLink、@Prop连用。
- @ObjectLink:@ObjectLink装饰的变量接收@Observed装饰的class的实例,应用于观察多层嵌套场景,和父组件的数据源构建双向同步。
2.2.3 管理组件状态
2.2.3.1 @State装饰器:组件内状态
- @State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。
- 在状态变量相关装饰器中,@State是最基础的,使变量拥有状态属性的装饰器,它也是大部分状态变量的数据源。
- @State装饰的变量,与声明式范式中的其他被装饰变量一样,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化。初始化也可选择使用命名参数机制从父组件完成初始化。
我们来个加法计数器案例:
@Entry@Componentstruct Index {@State count:number=0build() {Row() {Column() {Text("总数:"+this.count)Divider()Button("加法").width(100).height(30).margin(10).onClick(()=>{this.count ++;})Divider()Button("减法").width(100).height(30).margin(10).onClick(()=>{this.count --;})}.width('100%')}.height('100%')}}
规则说明
@State变量装饰器 | 说明 |
---|---|
装饰器参数 | 无 |
同步类型 | 不与父组件中任何类型的变量同步。 |
允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。嵌套类型的场景请参考观察变化。 类型必须被指定。 不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。 说明 |
被装饰变量的初始值 | 必须本地初始化。 |
其他内容参考官网:内容限制,使用能够引起UI变化条件
2.2.3.2 @Prop装饰器:父子单向同步
- @Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。
- @Prop变量允许在本地修改,但修改后的变化不会同步回父组件。
- 当父组件中的数据源更改时,与之相关的@Prop装饰的变量都会自动更新。如果子组件已经在本地修改了@Prop装饰的相关变量值,而在父组件中对应的@State装饰的变量被修改后,子组件本地修改的@Prop装饰的相关变量值将被覆盖。
@Entry
@Component
struct StateManagement {// @State 必须初始化@State name: string = '帝心'build() {Row() {Column() {Text(this.name).StateManagement_textSty()Button('修改数据').StateManagement_btnStyle(() => {this.name = this.name === '帝心' ? '庄生' : '帝心'})Divider()StateManagement_prop({ content_prop: this.name })Divider()}.width('100%')}.height('100%')}
}// 存放一个 @Prop 装饰的状态数据。方便父子组件之间进行数据传递和同步 State ----> prop
@Component
struct StateManagement_prop {@Prop content_prop: stringbuild() {Column() {Text('prop:' + this.content_prop).StateManagement_textSty()Button('修改prop数据').StateManagement_btnStyle(() => {this.content_prop = 'HarmonyOS4.0'})}}
}// 同样的样式记得复用 text
@Extend(Text) function StateManagement_textSty() {.fontSize(30).fontWeight(FontWeight.Bold).fontColor(Color.Green)
}// button样式
@Extend(Button) function StateManagement_btnStyle(click: Function) {.fontSize(30).onClick(() => {click()})
}
- 我们可以通过观察,当我们修改父组件,子组件的值发生变化,但是子组件的值发生变化,不会影响父组件的值的变化
限制条件
- @Prop修饰复杂类型时是深拷贝,在拷贝的过程中除了基本类型、Map、Set、Date、Array外,都会丢失类型。
- @Prop装饰器不能在@Entry装饰的自定义组件中使用。
规则限制
@Prop变量装饰器 | 说明 |
---|---|
装饰器参数 | 无 |
同步类型 | 单向同步:对父组件状态变量值的修改,将同步给子组件@Prop装饰的变量,子组件@Prop变量的修改不会同步到父组件的状态变量上 |
允许装饰的变量类型 | string、number、boolean、enum类型。 不支持any,不允许使用undefined和null。 必须指定类型。 在父组件中,传递给@Prop装饰的值不能为undefined或者null,反例如下所示。 CompA ({ aProp: undefined }) CompA ({ aProp: null }) @Prop和数据源类型需要相同,有以下三种情况(数据源以@State为例): - @Prop装饰的变量和父组件状态变量类型相同,即@Prop : S和@State : S,示例请参考父组件@State到子组件@Prop简单数据类型同步。 - 当父组件的状态变量为数组时,@Prop装饰的变量和父组件状态变量的数组项类型相同,即@Prop : S和@State : Array - 当父组件状态变量为Object或者class时,@Prop装饰的变量和父组件状态变量的属性类型相同,即@Prop : S和@State : { propA: S },示例请参考从父组件中的@State类对象属性到@Prop简单类型的同步。 |
被装饰变量的初始值 | 允许本地初始化。 |
2.2.3.3 @Link装饰器:父子双向同步
- 子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。
- @Link装饰的变量与其父组件中的数据源共享相同的值。
@Entry
@Component
struct StateManagement {// @State 必须初始化: 本地初始化@State name: string = '我是初始化本地数据'build() {Row() {Column() {Text(this.name).StateManagement_textSty()Button('修改数据').StateManagement_btnStyle(() => {this.name = this.name === '我是初始化本地数据' ? '我是初始化本地数据01' : '我是初始化本地数据02'})Divider()StateManagement_prop({ content_prop: this.name })Divider()StateManagement_Link({ content_link: $name })}.width('100%')}.height('100%')}
}// 存放一个 @Prop 装饰的状态数据。方便父子组件之间进行数据传递和同步 State ----> prop
@Component
struct StateManagement_prop {@Prop content_prop: stringbuild() {Column() {Text('prop:' + this.content_prop).StateManagement_textSty()Button('修改prop数据').StateManagement_btnStyle(() => {this.content_prop = 'HarmonyOS4.0'})}}
}@Component
struct StateManagement_Link {@Link content_link: stringbuild() {Column() {Text('link:' + this.content_link).StateManagement_textSty()Button('修改Link数据').StateManagement_btnStyle(() => {this.content_link = 'HarmonyOS4.0---Link'})}}
}// 同样的样式记得复用 text
@Extend(Text) function StateManagement_textSty() {.fontSize(30).fontWeight(FontWeight.Bold).fontColor(Color.Green)
}// button样式
@Extend(Button) function StateManagement_btnStyle(click: Function) {.fontSize(30).onClick(() => {click()})
}
我们可以看到使用link 实现了双向绑定
限制条件
- @Link装饰器不能在@Entry装饰的自定义组件中使用。
规则
@Link变量装饰器 | 说明 |
---|---|
装饰器参数 | 无 |
同步类型 | 双向同步。 父组件中@State, @StorageLink和@Link 和子组件@Link可以建立双向数据同步,反之亦然。 |
允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。嵌套类型的场景请参考观察变化。 类型必须被指定,且和双向绑定状态变量的类型相同。 不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。 说明 |
被装饰变量的初始值 | 无,禁止本地初始化。 |
2.2.3.4 @Provide装饰器和@Consume装饰器:同级组件通信
@Provide/@Consume装饰的状态变量有以下特性:
- @Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
- 后代通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步,与@State/@Link不同的是,前者可以在多层级的父子组件之间传递。
- @Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,变量类型必须相同。
- @Provide和@Consume通过相同的变量名或者相同的变量别名绑定时,@Provide修饰的变量和@Consume修饰的变量是一对多的关系。不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的@Provide装饰的变量。
@Component
struct CompD {@Consume reviewVotes: number;@State url: string="组件"build() {Column() {Text(`reviewVotes(${this.url})`)Button(`reviewVotes(${this.reviewVotes}), give +1`).onClick(() => this.reviewVotes += 1)}.width('50%')}
}@Entry
@Component
struct CompA {@Provide reviewVotes: number = 0;build() {Column() {Button(`reviewVotes(${this.reviewVotes}), give +1`).onClick(() => this.reviewVotes += 1)CompD({url:"组件一"})Divider()CompD({url:"组件二"})}}
}
我们可以发现同级组件中发生变化相互影响
规则
@State的规则同样适用于@Provide,差异为@Provide还作为多层后代的同步源。
@Provide变量装饰器 | 说明 |
---|---|
装饰器参数 | 别名:常量字符串,可选。 如果指定了别名,则通过别名来绑定变量;如果未指定别名,则通过变量名绑定变量。 |
同步类型 | 双向同步。 从@Provide变量到所有@Consume变量以及相反的方向的数据同步。双向同步的操作与@State和@Link的组合相同。 |
允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。嵌套类型的场景请参考观察变化。 不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。 必须指定类型。@Provide变量的@Consume变量的类型必须相同。 说明 |
被装饰变量的初始值 | 必须指定。 |
@Consume变量装饰器 | 说明 |
---|---|
装饰器参数 | 别名:常量字符串,可选。 如果提供了别名,则必须有@Provide的变量和其有相同的别名才可以匹配成功;否则,则需要变量名相同才能匹配成功。 |
同步类型 | 双向:从@Provide变量(具体请参见@Provide)到所有@Consume变量,以及相反的方向。双向同步操作与@State和@Link的组合相同。 |
允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。嵌套类型的场景请参考观察变化。 不支持any,不允许使用undefined和null。 必须指定类型。@Provide变量的@Consume变量的类型必须相同。 说明 |
被装饰变量的初始值 | 无,禁止本地初始化。 |
2.2.3.5 @Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
- 上文所述的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink装饰器。
- @ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:
- 被@Observed装饰的类,可以被观察到属性的变化;
- 子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
- 单独使用@Observed是没有任何作用的,需要搭配@ObjectLink或者@Prop使用。
@Component
struct CompD {@ObjectLink b: ClassBbuild() {Column() {Text("@Component"+this.b.a.c)Text("@Component"+this.b.b)}.width('50%')}
}@Entry
@Component
struct CompA {@State b: ClassB=new ClassB(new ClassA(1),100)build() {Column() {CompD({b:this.b})Divider()Button("修改值").width(100).height(30).margin(10).onClick(()=>{this.b=new ClassB(new ClassA(200),300)})}}
}class ClassA {public c: number;constructor(c: number) {this.c = c;}
}@Observed
class ClassB {public a: ClassA;public b: number;constructor(a: ClassA, b: number) {this.a = a;this.b = b;}
}
我们可以看到当我们点击修改时,嵌套的值发生了变化
限制条件
- 使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。
- @ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
规则
@Observed类装饰器 | 说明 |
---|---|
装饰器参数 | 无 |
类装饰器 | 装饰class。需要放在class的定义前,使用new创建类对象。 |
@ObjectLink变量装饰器 | 说明 |
---|---|
装饰器参数 | 无 |
同步类型 | 不与父组件中的任何类型同步变量。 |
允许装饰的变量类型 | 必须为被@Observed装饰的class实例,必须指定类型。 不支持简单类型,可以使用@Prop。 @ObjectLink的属性是可以改变的,但是变量的分配是不允许的,也就是说这个装饰器装饰变量是只读的,不能被改变。 |
被装饰变量的初始值 | 不允许。 |
2.2.4 应用组件状态
如果开发者要实现应用级的,或者多个页面的状态数据共享,就需要用到应用级别的状态管理的概念。ArkTS根据不同特性,提供了多种应用状态管理的能力:
- LocalStorage:页面级UI状态存储,通常用于UIAbility内、页面间的状态共享。
- AppStorage:特殊的单例LocalStorage对象,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储;
- PersistentStorage:持久化存储UI状态,通常和AppStorage配合使用,选择AppStorage存储的数据写入磁盘,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同;
- Environment:应用程序运行的设备的环境参数,环境参数会同步到AppStorage中,可以和AppStorage搭配使用。
2.2.4.1 LocalStorage:页面级UI状态存储
- LocalStorage是页面级的UI状态存储,通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage也可以在UIAbility内,页面间共享状态。
- 应用程序可以创建多个LocalStorage实例,LocalStorage实例可以在页面内共享,也可以通过GetShared接口,获取在UIAbility里创建的GetShared,实现跨页面、UIAbility内共享。
- 组件树的根节点,即被@Entry装饰的@Component,可以被分配一个LocalStorage实例,此组件的所有子组件实例将自动获得对该LocalStorage实例的访问权限;
- 被@Component装饰的组件最多可以访问一个LocalStorage实例和AppStorage,未被@Entry装饰的组件不可被独立分配LocalStorage实例,只能接受父组件通过@Entry传递来的LocalStorage实例。一个LocalStorage实例在组件树上可以被分配给多个组件。
- LocalStorage中的所有属性都是可变的。
// 创建新实例并使用给定对象初始化
let storage = new LocalStorage({ 'PropA': 47 });
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct CompA {// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@LocalStorageProp('PropA') storProp1: number = 1;build() {Column() {Text("默认数字:"+this.storProp1).StateManagement_textSty()Button('修改数据').StateManagement_btnStyle(() => {this.storProp1 += 1})Divider()StateManagement_prop()Divider()StateManagement_link()}}
}// 存放一个 @Prop 装饰的状态数据。方便父子组件之间进行数据传递和同步 State ----> prop
@Component
struct StateManagement_prop {// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@LocalStorageProp('PropA') storProp2: number = 2;build() {Column() {Text('prop:' + this.storProp2).StateManagement_textSty()Button('修改prop数据').StateManagement_btnStyle(() => {this.storProp2++;})}}
}@Component
struct StateManagement_link {// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@LocalStorageLink('PropA') storProp2: number = 2;build() {Column() {Text('link:' + this.storProp2).StateManagement_textSty()Button('修改link数据').StateManagement_btnStyle(() => {this.storProp2++;})}}
}// 同样的样式记得复用 text
@Extend(Text) function StateManagement_textSty() {.fontSize(30).fontWeight(FontWeight.Bold).fontColor(Color.Green)
}// button样式
@Extend(Button) function StateManagement_btnStyle(click: Function) {.fontSize(30).onClick(() => {click()})
}
- 我们可以发现当我们修改prop 数据,自身发生变化
- 修改link 是,全部数据发生变化
规则
@LocalStorageProp变量装饰器 | 说明 |
---|---|
装饰器参数 | key:常量字符串,必填(字符串需要有引号)。 |
允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。嵌套类型的场景请参考观察变化和行为表现。 类型必须被指定,且必须和LocalStorage中对应属性相同。不支持any,不允许使用undefined和null。 |
同步类型 | 单向同步:从LocalStorage的对应属性到组件的状态变量。组件本地的修改是允许的,但是LocalStorage中给定的属性一旦发生变化,将覆盖本地的修改。 |
被装饰变量的初始值 | 必须指定,如果LocalStorage实例中不存在属性,则作为初始化默认值,并存入LocalStorage中。 |
@LocalStorageLink变量装饰器 | 说明 |
---|---|
装饰器参数 | key:常量字符串,必填(字符串需要有引号)。 |
允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。嵌套类型的场景请参考观察变化和行为表现。 类型必须被指定,且必须和LocalStorage中对应属性相同。不支持any,不允许使用undefined和null。 |
同步类型 | 双向同步:从LocalStorage的对应属性到自定义组件,从自定义组件到LocalStorage对应属性。 |
被装饰变量的初始值 | 必须指定,如果LocalStorage实例中不存在属性,则作为初始化默认值,并存入LocalStorage中。 |
2.2.4.2 AppStorage:应用全局的UI状态存储
- AppStorage是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。
- 和AppStorage不同的是,LocalStorage是页面级的,通常应用于页面内的数据共享。而AppStorage是应用级的全局状态共享,还相当于整个应用的“中枢”,持久化数据PersistentStorage和环境变量Environment都是通过和AppStorage中转,才可以和UI交互。
- AppStorage是在应用启动的时候会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorage将在应用运行过程保留其属性。属性通过唯一的键字符串值访问。
- AppStorage可以和UI组件同步,且可以在应用业务逻辑中被访问。
- AppStorage中的属性可以被双向同步,数据可以是存在于本地或远程设备上,并具有不同的功能,比如数据持久化(详见PersistentStorage)。这些数据是通过业务逻辑中实现,与UI解耦,如果希望这些数据在UI中使用,需要用到@StorageProp和@StorageLink。
@StorageProp
- @StorageProp(key)是和AppStorage中key对应的属性建立单向数据同步,我们允许本地改变的发生,但是对于@StorageProp,本地的修改永远不会同步回AppStorage中,相反,如果AppStorage给定key的属性发生改变,改变会被同步给@StorageProp,并覆盖掉本地的修改。
AppStorage.SetOrCreate('PropC', 10086);
// 创建新实例并使用给定对象初始化
let storage = new LocalStorage({ 'PropA': 47 });
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct CompA {// 全局 @StorageProp与AppStorage进行绑定@StorageProp('PropC') storLink: number = 1;// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@LocalStorageProp('PropA') storProp1: number = 1;build() {Column() {Text("默认数字:"+this.storProp1).StateManagement_textSty()Button('修改数据页面ui').StateManagement_btnStyle(() => {this.storProp1 += 1})Text("默认数字:"+this.storLink).StateManagement_textSty()Button('修改数据全局ui').StateManagement_btnStyle(() => {this.storLink += 1})}}
}// 存放一个 @Prop 装饰的状态数据。方便父子组件之间进行数据传递和同步 State ----> prop
@Component
struct StateManagement_prop {// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@LocalStorageProp('PropA') storProp2: number = 2;build() {Column() {Text('prop:' + this.storProp2).StateManagement_textSty()Button('修改prop数据').StateManagement_btnStyle(() => {this.storProp2++;})}}
}@Component
struct StateManagement_link {// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@LocalStorageLink('PropA') storProp2: number = 2;build() {Column() {Text('link:' + this.storProp2).StateManagement_textSty()Button('修改link数据').StateManagement_btnStyle(() => {this.storProp2++;})}}
}// 同样的样式记得复用 text
@Extend(Text) function StateManagement_textSty() {.fontSize(30).fontWeight(FontWeight.Bold).fontColor(Color.Green)
}// button样式
@Extend(Button) function StateManagement_btnStyle(click: Function) {.fontSize(30).onClick(() => {click()})
}
我们可以看到一个全局的修改,一个是页面的修改并不会互相影响
规则
@StorageProp变量装饰器 | 说明 |
---|---|
装饰器参数 | key:常量字符串,必填(字符串需要有引号)。 |
允许装饰的变量类型 | Object class、string、number、boolean、enum类型,以及这些类型的数组。嵌套类型的场景请参考观察变化和行为表现。 类型必须被指定,且必须和LocalStorage中对应属性相同。不支持any,不允许使用undefined和null。 |
同步类型 | 单向同步:从AppStorage的对应属性到组件的状态变量。 组件本地的修改是允许的,但是AppStorage中给定的属性一旦发生变化,将覆盖本地的修改。 |
被装饰变量的初始值 | 必须指定,如果AppStorage实例中不存在属性,则作为初始化默认值,并存入AppStorage中。 |
@StorageLink
- 本地修改发生,该修改会被写回AppStorage中;
- AppStorage中的修改发生后,该修改会被同步到所有绑定AppStorage对应key的属性上,包括单向(@StorageProp和通过Prop创建的单向绑定变量)、双向(@StorageLink和通过Link创建的双向绑定变量)变量和其他实例(比如PersistentStorage)。
AppStorage.SetOrCreate('PropC', 10086);
// 创建新实例并使用给定对象初始化
let storage = new LocalStorage({ 'PropA': 47 });
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct CompA {// 全局 @StorageProp与AppStorage进行绑定@StorageProp('PropC') storLink: number = 1;// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@LocalStorageProp('PropA') storProp1: number = 1;@StorageLink("PropC") storLink1: number = 1;build() {Column() {Text("默认数字:"+this.storProp1).StateManagement_textSty()Button('修改数据页面ui').StateManagement_btnStyle(() => {this.storProp1 += 1})Text("默认数字:"+this.storLink).StateManagement_textSty()Button('修改数据全局ui-prop').StateManagement_btnStyle(() => {this.storLink += 1})Text("默认数字:"+this.storLink1).StateManagement_textSty()Button('修改数据全局ui-link').StateManagement_btnStyle(() => {this.storLink1 += 1})Divider()StateManagement_link()}}
}@Component
struct StateManagement_link {// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@StorageLink('PropC') storProp2: number = 2;build() {Column() {Text('link:' + this.storProp2).StateManagement_textSty()Button('修改link数据').StateManagement_btnStyle(() => {this.storProp2++;})}}
}// 同样的样式记得复用 text
@Extend(Text) function StateManagement_textSty() {.fontSize(30).fontWeight(FontWeight.Bold).fontColor(Color.Green)
}// button样式
@Extend(Button) function StateManagement_btnStyle(click: Function) {.fontSize(30).onClick(() => {click()})
}
我们可以观察到当我们点击全局修改后,使用了同一个Key的值进行修改
规则
@StorageLink变量装饰器 | 说明 |
---|---|
装饰器参数 | key:常量字符串,必填(字符串需要有引号)。 |
允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。嵌套类型的场景请参考观察变化和行为表现。 类型必须被指定,且必须和AppStorage中对应属性相同。不支持any,不允许使用undefined和null。 |
同步类型 | 双向同步:从AppStorage的对应属性到自定义组件,从自定义组件到AppStorage对应属性。 |
被装饰变量的初始值 | 必须指定,如果AppStorage实例中不存在属性,则作为初始化默认值,并存入AppStorage中。 |
2.2.4.3 PersistentStorage:持久化存储UI状态
- PersistentStorage是应用程序中的可选单例对象。此对象的作用是持久化存储选定的AppStorage属性,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。
- PersistentStorage将选定的AppStorage属性保留在设备磁盘上。应用程序通过API,以决定哪些AppStorage属性应借助PersistentStorage持久化。UI和业务逻辑不直接访问PersistentStorage中的属性,所有属性访问都是对AppStorage的访问,AppStorage中的更改会自动同步到PersistentStorage。
AppStorage.SetOrCreate('PropC', 10086);
// 创建新实例并使用给定对象初始化
let storage = new LocalStorage({ 'PropA': 47 });
PersistentStorage.PersistProp('PropB', 11100215);
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct CompA {// 全局 @StorageProp与AppStorage进行绑定@StorageProp('PropC') storLink: number = 1;// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@LocalStorageProp('PropA') storProp1: number = 1;@StorageLink("PropC") storLink1: number = 1;build() {Column() {Text("默认数字:"+this.storProp1).StateManagement_textSty()Button('修改数据页面ui').StateManagement_btnStyle(() => {this.storProp1 += 1})Text("默认数字:"+this.storLink).StateManagement_textSty()Button('修改数据全局ui-prop').StateManagement_btnStyle(() => {this.storLink += 1})Text("默认数字:"+this.storLink1).StateManagement_textSty()Button('修改数据全局ui-link').StateManagement_btnStyle(() => {this.storLink1 += 1})Divider()StateManagement_link()Divider()Text(AppStorage.Get('PropB')).StateManagement_textSty()}}
}@Component
struct StateManagement_link {// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@StorageLink('PropC') storProp2: number = 2;build() {Column() {Text('link:' + this.storProp2).StateManagement_textSty()Button('修改link数据').StateManagement_btnStyle(() => {this.storProp2++;})}}
}// 同样的样式记得复用 text
@Extend(Text) function StateManagement_textSty() {.fontSize(30).fontWeight(FontWeight.Bold).fontColor(Color.Green)
}// button样式
@Extend(Button) function StateManagement_btnStyle(click: Function) {.fontSize(30).onClick(() => {click()})
}
- 调用PersistProp初始化PersistentStorage,首先查询在PersistentStorage本地文件中是否存在“PropB”,查询结果为不存在,因为应用是第一次安装。
- 接着查询属性“PropB”在AppStorage中是否存在,依旧不存在。
- 退出的时候会自动保存,再次进入读取到参数
2.2.4.4 Environment:设备环境查询
开发者如果需要应用程序运行的设备的环境参数,以此来作出不同的场景判断,比如多语言,暗黑模式等,需要用到Environment设备环境查询。
AppStorage.SetOrCreate('PropC', 10086);
let storage = new LocalStorage({ 'PropA': 47 });
Environment.EnvProp('languageCode', 'en');
@Entry(storage)
@Component
struct CompA {// 全局 @StorageProp与AppStorage进行绑定@StorageProp('PropC') storLink: number = 1;// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@LocalStorageProp('PropA') storProp1: number = 1;@StorageLink("PropC") storLink1: number = 1;build() {Column() {Text("默认数字:"+this.storProp1).StateManagement_textSty()Button('修改数据页面ui').StateManagement_btnStyle(() => {this.storProp1 += 1})Text("默认数字:"+this.storLink).StateManagement_textSty()Button('修改数据全局ui-prop').StateManagement_btnStyle(() => {this.storLink += 1})Text("默认数字:"+this.storLink1).StateManagement_textSty()Button('修改数据全局ui-link').StateManagement_btnStyle(() => {this.storLink1 += 1})Divider()StateManagement_link()Divider()Text(AppStorage.Get('languageCode')).StateManagement_textSty()}}
}@Component
struct StateManagement_link {// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定@StorageLink('PropC') storProp2: number = 2;build() {Column() {Text('link:' + this.storProp2).StateManagement_textSty()Button('修改link数据').StateManagement_btnStyle(() => {this.storProp2++;})}}
}// 同样的样式记得复用 text
@Extend(Text) function StateManagement_textSty() {.fontSize(30).fontWeight(FontWeight.Bold).fontColor(Color.Green)
}// button样式
@Extend(Button) function StateManagement_btnStyle(click: Function) {.fontSize(30).onClick(() => {click()})
}
2.2.5 其他状态
2.2.5.1 @Watch装饰器:状态变量更改通知
@Watch用于监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用。@Watch在ArkUI框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当在严格相等为false的情况下,就会触发@Watch的回调。
@Component
struct TotalView {@Prop @Watch('onCountUpdated') count: number;@State total: number = 0;// @Watch cbonCountUpdated(propName: string): void {this.total += this.count;}build() {Text(`Total: ${this.total}`)}
}@Entry
@Component
struct CountModifier {@State count: number = 0;build() {Column() {Button('add to basket').onClick(() => {this.count++})TotalView({ count: this.count })}}
}
简单来说,就是对某个属性的监听
规则
@Watch补充变量装饰器 | 说明 |
---|---|
装饰器参数 | 必填。常量字符串,字符串需要有引号。是(string) => void自定义成员函数的方法的引用。 |
可装饰的自定义组件变量 | 可监听所有装饰器装饰的状态变量。不允许监听常规变量。 |
装饰器的顺序 | 建议@State、@Prop、@Link等装饰器在@Watch装饰器之前。 |
2.2.5.2 $$语法:内置组件双向同步
$$运算符为系统内置组件提供TS变量的引用,使得TS变量和系统内置组件的内部状态保持同步。
// xxx.ets
@Entry
@Component
struct RefreshExample {@State isRefreshing: boolean = false@State counter: number = 0build() {Column() {Text('Pull Down and isRefreshing: ' + this.isRefreshing).fontSize(30).margin(10)Refresh({ refreshing: $$this.isRefreshing, offset: 120, friction: 100 }) {Text('Pull Down and refresh: ' + this.counter).fontSize(30).margin(10)}.onStateChange((refreshStatus: RefreshStatus) => {console.info('Refresh onStatueChange state is ' + refreshStatus)})}}
}
2.3 渲染控制
ArkUI通过自定义组件的build()函数和@builder装饰器中的声明式UI描述语句构建相应的UI。在声明式描述语句中开发者除了使用系统组件外,还可以使用渲染控制语句来辅助UI的构建,这些渲染控制语句包括控制组件是否显示的条件渲染语句,基于数组数据快速生成组件的循环渲染语句以及针对大数据量场景的数据懒加载语句。
2.3.1 if/else:条件渲染
ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态,使用if、else和else if渲染对应状态下的UI内容。
// 使用Environment.EnvProp将设备运行languageCode存入AppStorage中;
Environment.EnvProp('languageCode', 'zh');
// 从AppStorage获取单向绑定的languageCode的变量
const lang: SubscribedAbstractProperty<string> = AppStorage.Prop('languageCode');
@Entry
@Component
struct RefreshExample {@State isRefreshing: boolean = false@State counter: number = 0build() {Column() {Text(lang.get())Divider()if (lang.get() === 'zh') {Text('下拉筛选状态: ' + this.isRefreshing).fontSize(30).margin(10)}else {Text('Pull Down and isRefreshing: ' + this.isRefreshing).fontSize(30).margin(10)}Refresh({ refreshing: $$this.isRefreshing, offset: 120, friction: 100 }) {if (lang.get() === 'zh') {Text('下拉刷新次数: ' + this.counter).fontSize(30).margin(10)}else{Text('Pull Down and refresh: ' + this.counter).fontSize(30).margin(10)}}.onStateChange((refreshStatus: RefreshStatus) => {console.info('Refresh onStatueChange state is ' + refreshStatus)})}}
}
2.3.2 ForEach:循环渲染
ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。
接口描述
ForEach(arr: Array,itemGenerator: (item: Array, index?: number) => void,keyGenerator?: (item: Array, index?: number): string => string
)
在ForEach循环渲染过程中,系统会为每个数组元素生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。
@Entry
@Component
struct ArticleList {@State simpleList: Array<number> = [1, 2, 3, 4, 5];build() {Column() {ForEach(this.simpleList,(item: string,index:number) => {Text(index+"- " + item)ArticleSkeletonView().margin({ top: 20 })},(item: string) => item)}.padding(20).width('100%').height('100%')}
}@Builder
function textArea(width: number | Resource | string = '100%', height: number | Resource | string = '100%') {Row().width(width).height(height).backgroundColor('#FFF2F3F4')
}@Component
struct ArticleSkeletonView {build() {Row() {Column() {textArea(80, 80)}.margin({ right: 20 })Column() {textArea('60%', 20)textArea('50%', 20)}.alignItems(HorizontalAlign.Start).justifyContent(FlexAlign.SpaceAround).height('100%')}.padding(20).borderRadius(12).backgroundColor('#FFECECEC').height(120).width('100%').justifyContent(FlexAlign.SpaceBetween)}
}
2.3.3 LazyForEach:数据懒加载
LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。
接口描述
LazyForEach(dataSource: IDataSource, // 需要进行数据迭代的数据源itemGenerator: (item: any, index?: number) => void, // 子组件生成函数keyGenerator?: (item: any, index?: number) => string // 键值生成函数
): void
IDataSource 接口
interface IDataSource {totalCount(): number; // 获得数据总数getData(index: number): Object; // 获取索引值对应的数据registerDataChangeListener(listener: DataChangeListener): void; // 注册数据改变的监听器unregisterDataChangeListener(listener: DataChangeListener): void; // 注销数据改变的监听器
}
registerDataChangeListener
interface DataChangeListener {onDataReloaded(): void; // 重新加载数据时调用onDataAdded(index: number): void; // 添加数据时调用onDataMoved(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换时调用onDataDeleted(index: number): void; // 删除数据时调用onDataChanged(index: number): void; // 改变数据时调用onDataAdd(index: number): void; // 添加数据时调用onDataMove(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换时调用onDataDelete(index: number): void; // 删除数据时调用onDataChange(index: number): void; // 改变数据时调用
}
- LazyForEach必须在容器组件内使用,仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。
- LazyForEach在每次迭代中,必须创建且只允许创建一个子组件。
- 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
- 允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。
- 键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件被框架忽略,从而无法在父容器内显示。
- LazyForEach必须使用DataChangeListener对象来进行更新,第一个参数dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新。
- 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,需要生成不同于原来的键值来触发组件刷新。
class BaseDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: string[] = [];public totalCount(): number {return 0;}public getData(index: number): string {return this.originDataArray[index];}// 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}// 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}// 通知LazyForEach组件需要重载所有子组件notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}// 通知LazyForEach组件需要在index对应索引处添加子组件notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}// 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}// 通知LazyForEach组件需要在index对应索引处删除该子组件notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}
}class MyDataSource extends BaseDataSource {private dataArray: string[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): string {return this.dataArray[index];}public addData(index: number, data: string): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: string): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}
}@Entry
@Component
struct MyComponent {private data: MyDataSource = new MyDataSource();aboutToAppear() {for (let i = 0; i <= 20; i++) {this.data.pushData(`Hello ${i}`)}}build() {List({ space: 3 }) {LazyForEach(this.data, (item: string,index:number) => {ArticleSkeletonView({text:item}).margin({ top: 20 })},(item: string) => item)}.cachedCount(5)}
}@Builder
function textArea(width: number | Resource | string = '100%', height: number | Resource | string = '100%',text: string = '') {Text(text)Row().width(width).height(height).backgroundColor('#FFF2F3F4')}@Component
struct ArticleSkeletonView {@State text:string ="Hello"build() {Row() {Column() {textArea(80, 80)}.margin({ right: 20 })Column() {textArea('60%', 20)textArea('50%', 20)}.alignItems(HorizontalAlign.Start).justifyContent(FlexAlign.SpaceAround).height('100%')}.padding(20).borderRadius(12).backgroundColor('#FFECECEC').height(120).width('100%').justifyContent(FlexAlign.SpaceBetween)}
}
键值生成规则是keyGenerator函数的返回值item。
到此:ArkTs基本语法学完了,请多看官网
相关文章:
HarmonyOs 4 (三) ArkTS语言
目录 一 认识ArkTs语言1.1 ArkTs1.2 基本结构 二 基本语法2.1 声明式UI2.1.1 创建组件2.1.1.1 无参数2.1.1.2 有参数2.1.1.3 组件样式2.1.1.4 组件方法2.1.1.5 组件嵌套 2.1.2 自定义组件2.1.2.1 基本结构2.1.2.2 成员函数/变量2.1.2.3 自定义组件的参数规定2.1.2.4 Build函数2…...
PostGIS学习教程九:空间连接
PostGIS学习教程九:空间连接 空间连接(spatial joins)是空间数据库的主要组成部分,它们允许你使用空间关系作为连接键(join key)来连接来自不同数据表的信息。我们认为“标准GIS分析”的大部分内容可以表示…...
C++ day56 两个字符串的删除操作 编辑距离
题目1:583 两个字符串的删除操作 题目链接:两个字符串的删除操作 对题目的理解 返回使两个单词word1和word2相同的最少删除多少个元素,两个单词至少包含一个字母,且仅包含小写字母 思路1:这道题与昨天的不同子序列…...
Android studio中如何生成jar包?
文章目录 需求背景目录结构gradle结构makeJar的语法解析 执行makeJar 任务拿到jar包 需求背景 别部门做C语言开发的同学开发了一个库,需要给我们Android端去调用。 我们拿到源码,首先需要做的是通过CMake去把C源码编译链接成动态库。 当然静态库也行&am…...
【2】基于多设计模式下的同步异步日志系统-设计模式
6. 相关技术知识补充 6.1 不定参函数 在初学C语⾔的时候,我们都⽤过printf函数进⾏打印。其中printf函数就是⼀个不定参函数,在函数内部可以根据格式化字符串中格式化字符分别获取不同的参数进⾏数据的格式化。 ⽽这种不定参函数在实际的使⽤中也⾮常…...
第十五届蓝桥杯模拟赛B组(第二期)C++
前言: 第一次做蓝桥模拟赛的博客记录,可能有很多不足的地方,现在将第十五届蓝桥杯模拟赛B组(第二期)的题目与代码与大家进行分享,我是用C做的,有好几道算法题当时自己做的也是一脸懵,…...
企业ERP软件定制开发要注意|app小程序搭建
企业ERP软件定制开发要注意|app小程序搭建 企业ERP软件定制开发是一项复杂而且关键的任务,它需要深入理解企业的需求和流程,并且以此为基础进行设计和开发。以下是一些关于企业ERP软件定制开发的注意事项。 首先,我们必须确保在进行定制开发之…...
系统架构设计-权限模块的设计
系统架构-权限模块的设计 如何评估一个研发人员技术水平,在大部分的情况下不是看其完成业务代码的好坏,更多的时候还是需要看这个研发人员从零构建一个完整项目的能力,在大公司中这样的机会可能相对较少,大部分的时间里都是对现有…...
IDEA切换Python虚拟环境
前言 因为之前一直使用的IDEA开发,换到VSCODE之后各种不习惯,特别是DEBUG的操作,特别难受,因此决心换回IDEA 环境配置 已有项目调整 进入Project 选择SDKs,新建Python 配置Conda以及虚拟环境 有就选择一个虚拟环境…...
《opencv实用探索·十一》opencv之Prewitt算子边缘检测,Roberts算子边缘检测和Sobel算子边缘检测
1、前言 边缘检测: 图像边缘检测是指在图像中寻找灰度、颜色、纹理等变化比较剧烈的区域,它们可能代表着物体之间的边界或物体内部的特征。边缘检测是图像处理中的一项基本操作,可以用于人脸识别、物体识别、图像分割等多个领域。 边缘检测…...
prime靶机打靶记录
靶机下载地址 https://download.vulnhub.com/prime/Prime_Series_Level-1.rar nmap搜索目标 使用nmap -sn 192.168.41.0/24找到目标靶机192.168.41.136 扫描端口,因为是靶机,所以速率直接调了10000 扫出来两个端口22和80,进行详细的扫描 没…...
树莓派,linux换清华源
清华源网址 https://mirrors.tuna.tsinghua.edu.cn/help/raspbian/ 更换软件源 鉴于国内网络环境下载各大镜像,软件包速度慢的问题,需要更换软件源,以防下载慢,且在本教程中,统一更换为清华源。 2.3.1 更换树莓派软…...
公有云迁移研究——AWS DMS
大纲 1 什么是DMS2 DMS的作用3 DMS在迁移的时候都做些什么4 在使用DMS的时候我们需要做些什么5 操作5.1 创建两个数据库终端节点5.2 创建迁移任务 6 可能遇到的问题7 总结 在本地机房或其他云往AWS上做迁移时,往往会遇到数据库迁移的任务。如果数据量不是特别大&…...
一起学docker系列之十七Docker Compose 与手动操作的比较与优势分析
目录 1 前言2 不使用 Docker Compose2.1 启动 MySQL 容器2.2 启动 Redis 容器2.3 启动微服务容器 3 使用 Docker Compose4 使用 Docker Compose 的优势5 结语参考地址 1 前言 在当今容器化应用的开发与部署中,容器编排工具的选择对于简化流程、提高效率至关重要。本…...
IP地址定位不准确的情况研究
在互联网的浩瀚海洋中,每一台连接到网络的设备都被赋予了一个独特的标识符,这就是IP地址。它就像是我们在线身份的一部分,帮助我们与他人进行通信,获取信息,以及享受各种网络服务。然而,由于各种原因&#…...
武汉凯迪正大KDZD5289硫化曲线测试仪(电脑无转子硫化仪)
电脑无转子硫化仪 硫化时间测试仪 硫化曲线仪 硫化曲线测试仪 武汉凯迪正大KDZD5289产品概述 KDZD5289硫化曲线测试仪(电脑无转子硫化仪)采用电脑控制进口温控仪进行准确控温,计算机适时进行数据处理并可进行统计、分析、存储对比等ÿ…...
Topic和Partition
作用 主题作为消息的一级分类, 分区是对二级分类。分区是Kafka可伸缩性和水平扩展的关键, 也是多副本机制保证可用性的基础。分区可以有一到多个副本, 每个副本对应1个日志文件, 每个日志文件对应1到多个日志分段。每个日志分段又可以细分为日志文件, 索引文件和快照文件。 创…...
算法通关村第十四关|黄金挑战|数据流的中位数
数据流的中位数 原题:力扣295. 设计一种数据结构可以支持添加整数和返回中位数的操作。 之前写过找中间用两个堆,这道题就可以使用一个大顶堆和一个小顶堆。 大顶堆存储比较小的元素,小顶堆存储比较大的元素。 class MedianFinder {Prio…...
挑选数据可视化工具:图表类型、交互功能与数据安全
作为一名数据分析师,我经常需要使用各种数据可视化工具来将数据以直观、清晰的方式呈现出来,以便更好地理解和分析。在市面上的众多可视化工具中,我根据实际需求和项目特点进行选择。本文将从以下几个角度对市面上的数据可视化工具进行对比&a…...
华纳云:有效解决服务器宕机的办法
服务器宕机可能是由多种原因引起的,包括硬件故障、软件问题、网络问题等。以下是一些简单的解决服务器宕机问题的办法: 检查硬件连接: 确保服务器的所有硬件连接正常。检查电源线、网络连接、存储设备连接等,确保没有松动或断开的…...
坦克大战-部分
通过键盘操控坦克移动,转弯,射击 消灭所有敌人可以过关 23个类,3个gif图片 wsad控制移动 j射击 砖墙限制移动,可以打穿;铁墙,限制移动,不能打穿;水&#x…...
OracleRac跨网段修改Public IP/VIP/Private IP/Scan IP
本验证于测试环境,生产操作需谨慎 现为测试环境,机器有且仅有两个网卡存在,需求修改Public IP/VIP/Private IP/Scan IP,把Public IP/VIP/Scan IP的网段改为Private IP的网段,Private IP于Public IP网段互换。 先停掉两…...
使用Pytorch从零开始实现BERT
生成式建模知识回顾: [1] 生成式建模概述 [2] Transformer I,Transformer II [3] 变分自编码器 [4] 生成对抗网络,高级生成对抗网络 I,高级生成对抗网络 II [5] 自回归模型 [6] 归一化流模型 [7] 基于能量的模型 [8] 扩散模型 I, 扩散模型 II…...
Python爬虫-新能源汽车销量榜
前言 本文是该专栏的第11篇,后面会持续分享python爬虫案例干货,记得关注。 本文以懂车平台的新能源汽车销量榜单为例,获取各车型的销量排行榜单数据。具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。 废话不多说,跟着笔者直接往下看正文详细内容。(附带…...
外包干了8个月,技术退步明显.......
先说一下自己的情况,大专生,18年通过校招进入武汉某软件公司,干了接近4年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…...
<JavaEE> volatile关键字 -- 保证内存可见性、禁止指令重排序
目录 一、内存可见性 1.1 Java内存模型(JMM) 1.2 内存可见性演示 二、指令重排序 三、关键字 volatile 一、内存可见性 1.1 Java内存模型(JMM) 1)什么是Java内存模型(JMM)?Java内存模型即Java Memory Model,简…...
docker安装mysql8
docker安装mysql8 docker search mysql:8 #搜索可以使用的msyql8的镜像 docker pull mysql:8.0.27 #拉去mysql8的镜像 创建挂载的宿主机目录 mkdir -p /data/mysql/mysql8/conf # 配置文件目录 mkdir -p /data/mysql/mysql8/data # 数据目录 touch /data/mysql/mysql8/conf/my.…...
消息丢失排查方法?
遇到丢消息问题,如果是单聊,群聊,聊天室,系统消息可以在开发者后台北极星自助查询一下消息是否发送成功。根据您实际发送的相关信息(发送者、接收者、时间、消息 ID ……)看是否可以查到消息 如果消息查不到…...
Linux 匿名页反向映射
1. 何为反向映射 正向映射: 用户进程在申请内存时,内核并不会立刻给其分配物理内存,而是先为其分配一段虚拟地址空间,当进程访问该虚拟地址空间时,触发page fault异常,异常处理流程中会为其分配物理页面&am…...
国内首个农业开源鸿蒙操作系统联合华为正式发布
2023年11月29日,在中国国际供应链促进博览会上,中信农业科技股份有限公司(简称“中信农业”)与深圳开鸿数字产业发展有限公司(简称“深开鸿”)以及华为技术有限公司(简称“华为”)联…...
谁做违法网站/企业品牌策划
引言 线程池很普通的老话题,讨论的很多.深入的不多,也就那些基础库中才能见到这种精妙完备的技巧.而本文随大流 想深入简述一种高效控制性强的一种线程池实现. 先引入一个概念, 惊群. 简单举个例子. 春天来了, 公园出现了很多麻雀. 而你恰巧有一个玉米粒. 扔出去, 立马无数麻雀…...
网站 黄色/如何做网站建设
核心代码 File file new File(PATH_LOGCAT);if (!file.exists()) {file.mkdirs();Log.e("wy","创建文件夹1");}全部代码 private void init(Context context) {if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {// 优先保…...
zblog换wordpress/网络推广怎么做
之前用的opencore来配置黑苹果结果卡在这个地方死活进不去 [IGPU] Scheduler Throttle Cap100ms 之后去bios里面把igpu关掉就行了。 选择高级 选择北桥 选择显示设置 把初始化IGPU关了。 我现在ASUS B360M的主板,蓝宝石白金vega56,i7-8700的cpu&…...
在线播放视频网站怎么做/网站运营推广方案
转载于:https://blog.csdn.net/u011700186/article/details/109452658 相关环境 MacOS 10.15.4 SecureCRT 8.7.0 问题描述 当某一个用户登录某一台服务器之后,我们可能会想要执行某些特定的命令或者脚本。比如,连接后我们想要自动切换到某…...
免费的建设网站软件下载/提升关键词排名seo软件
方式1、 直接保存视频预览的数据帧 在预览方法中imgData数据就是视频帧 android默认的视频采集格式是NV21,(属于YUV420) 在onPreviewFrame中传进来的byte[] data即为NV21格式。 NV21 的存储格式是,以4 X 4 图片为例子 占用内…...
网站登录界面模板/夸克搜索
参考网址1: iOS消息推送机制的实现 http://www.cnblogs.com/qq78292959/archive/2012/07/16/2593651.html 参考网址2: iOS 推送的服务端实现 http://www.dozer.cc/2013/03/push-notifications-server-side-implement/ 参考网址3: iOS 证书与推…...