作者:HarderCoder
ArkTS
ArkTS围绕应用开发在 TypeScript (简称TS)生态基础上做了进一步扩展,继承了TS的所有特性,是TS的超集
ArkTS在TS的基础上扩展了struct和很多的装饰器以达到描述UI和状态管理的目的
基本语法
自定义组件必须使用struct定义,并且被Component装饰器修饰
@Component 表示这是一个自定义组件
@Entry 表示该自定义组件为入口组件
@State 表示组件中的状态变量,状态变量变化会触发UI刷新
UI描述 :以声明式的方式来描述UI的结构
自定义组件:可复用的UI单元,可组合其他组件
@Builder/ @BuilderParam
特殊的封装UI描述的方法,细粒度的封装和复用UI描述
- 我们可以在全局或者自定义组件内定义构建函数,使用构建函数可以将更细力度的复用UI
- @Builder 装饰的函数想要被接收,接收的参数类型必须被@BuilderParam装饰并且它俩的类型得匹配才可以
比如我们写卡片时都有固定的模板,圆角为8背景色为白色,我们就可以像下面这样封装
@Component
struct MyCard {
@BuilderParam child: () => void
build() {
Row() {
this.child()
}
.width("100%")
.borderRadius(8)
.backgroundColor(Color.White)
}
}
然后使用的时候配合上尾随闭包看起来会更自然
@Component
struct CustomContainerUser {
@State text: string = 'header';
build() {
Column() {
// 创建MyCard,在创建MyCard时,通过其后紧跟一个大括号“{}”形成尾随闭包
// 作为传递给子组件MyCard @BuilderParam child: () => void的参数
MyCard() {
Text("我是一个卡片")
}
}
}
}
@Extend
扩展内置组件和封装属性样式,更灵活地组合内置组件
@Styles
- @Styles装饰器可以将多条样式设置提炼成一个方法,定义在组件内容或全局,从而被复用
- @Styles仅支持 通用属性 和 通用事件 且不支持参数
// 定义在全局的@Styles封装的样式
@Styles function globalFancy () {
.width(150)
.height(100)
.backgroundColor(Color.Pink)
}
@Entry
@Component
struct FancyUse {
@State heightValue: number = 100
// 定义在组件内的@Styles封装的样式
@Styles fancy() {
.width(200)
.height(this.heightValue)
.backgroundColor(Color.Yellow)
.onClick(() => {
this.heightValue = 200
})
}
build() {
Column({ space: 10 }) {
// 使用全局的@Styles封装的样式
Text('FancyA')
.globalFancy ()
.fontSize(30)
// 使用组件内的@Styles封装的样式
Text('FancyB')
.fancy()
.fontSize(30)
}
}
}
@Extend
@Extend,用于扩展原生组件样式,仅能定义在全局,它支持参数
我们开发过程中经常需要定义字体宏进行使用,在ArkTS中我们可以为Text扩展样式来实现
@Extend(Text)
function PFFont(font: string = "PingFang") {
.fontFamily(font)
}
// PF_S可以调用预定义的PFFont
@Extend(Text)
function PF_S(size: number = 14) {
.PFFont("PingFang-Sem")
.fontSize(size)
}
@Extend(Text)
function PF_R(size: number = 14) {
.PFFont("PingFang-Regular")
.fontSize(size)
}
stateStyles
多态样式,可以依据组件的内部状态的不同,设置不同样式,目前支持的状态有
- focused:获焦态。
- normal:正常态。
- pressed:按压态。
- disabled:不可用态
状态管理
- ArkTS的组件状态管理分为 管理组件拥有的状态 和 管理应用拥有的状态
- Components部分的装饰器为组件级别的状态管理,Application部分为应用的状态管理
- 组件的状态传递和同步类型分为 只读的单向传递 和 可变更的双向传递。
状态管理的总则
- 一般Prop结尾的代表的是从上到下的单向数据流动,下层组件可以修改值,但不会回传到上层组件。上层组件修改值之后会覆盖掉下层组件本地修改的值
- 一般Link结尾的代表的是上层组件和下层组件的双向数据流动,下层组件修改值之后会同步回上层组件
可观察的变化
非 @ObjectLink和@Observed
- 当装饰的数据类型为boolean、string、number类型时,可以同步观察到数值的变化
- 当装饰的数据类型为class或者Object时,可以观察到赋值和属性赋值的变化。仅限第一层属性的变化
- 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化
@ObjectLink和@Observed
@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步
- 被@Observed装饰的类,可以被观察到属性的变化;
- 子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
- 单独使用@Observed是没有任何作用的,需要搭配@ObjectLink或者@Prop使用
双向数据流动的设计原理
- 在初次渲染时,上层组件的状态变量通过$state传递给下层组件,下层组件拿到状态变量后,将Link包装类的this指针注册给上层组件的状态变量
- 上层组件的状态变量发生变化后,会遍历依赖这个状态变量的所有组件以及我们在第一步注册的Link包装类进行更新。Link包装类更新又会导致依赖它的组件更新
- 下层组件的Link包装类变化时,调用上层组件传下来的状态变量的set方法更新状态变量的数值。然后上层组件和下层组件分别遍历各自的依赖状态变量的组件进行更新
管理组件拥有的状态
@State装饰器:组件内状态
- @State装饰的变量拥有其所属组件的状态,可以作为其子组件单向和双向同步的数据源。当其数值改变时,会引起相关组件的渲染刷新。
- @State变量可以从父组件初始化,也可以从组件内初始化。如果从父组件初始化,组件内的初始化会被覆盖
- 它的初始化规则如下
框架行为
- 当状态变量被改变时,查询依赖该状态变量的组件;
- 执行依赖该状态变量的组件的更新方法,组件更新渲染;
- 和该状态变量不相关的组件或者UI描述不会发生重新渲染,从而实现页面渲染的按需更新
@Prop装饰器:父子单向同步
- @Prop装饰的变量可以和父组件建立单向同步关系,@Prop装饰的变量是可变的,但修改不会同步回父组件,当父组件的@State变化时,本地修改的@Prop会被覆盖
- 它的初始化规则如下
框架行为
- 初始渲染:
- 执行父组件的build()函数将创建子组件的新实例,将数据源传递给子组件;
- 初始化子组件@Prop装饰的变量。
- 更新:
- 子组件@Prop更新时,更新仅停留在当前子组件,不会同步回父组件;
- 当父组件的数据源更新时,子组件的@Prop装饰的变量将被来自父组件的数据源重置,所有@Prop装饰的本地的修改将被父组件的更新覆盖。
@Link装饰器:父子双向同步
- @Link装饰的变量和父组件构建双向同步关系的状态变量,父组件会接受来自@Link装饰的变量的修改的同步,父组件的更新也会同步给@Link装饰的变量。
- @Link装饰的变量与其父组件中的数据源共享相同的值
- @Link装饰器不能在@Entry装饰的自定义组件中使用
- 它的初始化规则如下
框架行为
@Link装饰的变量和其所属的自定义组件共享生命周期。
- 初始渲染:执行父组件的build()函数后将创建子组件的新实例。初始化过程如下:
- 必须指定父组件中的@State变量,用于初始化子组件的@Link变量。子组件的@Link变量值与其父组件的数据源变量保持同步(双向数据同步)。
- 父组件的@State状态变量包装类通过构造函数传给子组件,子组件的@Link包装类拿到父组件的@State的状态变量后,将当前@Link包装类this指针注册给父组件的@State变量。
- @Link的数据源的更新:即父组件中状态变量更新,引起相关子组件的@Link的更新。处理步骤:
- 通过初始渲染的步骤可知,子组件@Link包装类把当前this指针注册给父组件。父组件@State变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(比如@Link包装类)。
- 通知@Link包装类更新后,子组件中所有依赖@Link状态变量的系统组件(elementId)都会被通知更新。以此实现父组件对子组件的状态数据同步。
- @Link的更新:当子组件中@Link更新后,处理步骤如下(以父组件为@State为例):
- @Link更新后,调用父组件的@State包装类的set方法,将更新后的数值同步回父组件。
- 子组件@Link和父组件@State分别遍历依赖的系统组件,进行对应的UI的更新。以此实现子组件@Link同步回父组件@State。
@Provide装饰器和@Consume装饰器:与后代组件双向同步
- @Provide/@Consume装饰的变量用于跨组件层级(多层组件)同步状态变量,可以不需要通过参数命名机制传递,通过alias(别名)或者属性名绑定。它的机制和@Link相似,但它可以跨多层管理数据,而@Link只能父子同步数据
- @Provide装饰的变量在祖先节点中,作为状态变量提供给后代。@Consume装饰的变量在后代组件中,绑定祖先节点提供的@Provide变量
- @Provider的初始化规则如下
4. @Consume的初始化规则如下
框架行为
- 初始渲染:
- @Provide装饰的变量会以map的形式,传递给当前@Provide所属组件的所有子组件;
- 子组件中如果使用@Consume变量,则会在map中查找是否有该变量名/alias(别名)对应的@Provide的变量,如果查找不到,框架会抛出JS ERROR;
- 在初始化@Consume变量时,和@State/@Link的流程类似,@Consume变量会保存在map中查找到的@Provide变量,并把自己注册给@Provide。
- 当@Provide装饰的数据变化时:
- 通过初始渲染的步骤可知,子组件@Consume已把自己注册给父组件。父组件@Provide变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(@Consume);
- 通知@Consume更新后,子组件所有依赖@Consume的系统组件(elementId)都会被通知更新。以此实现@Provide对@Consume状态数据同步。
- 当@Consume装饰的数据变化时:
- 通过初始渲染的步骤可知,子组件@Consume持有@Provide的实例。在@Consume更新后调用@Provide的更新方法,将更新的数值同步回@Provide,以此实现@Consume向@Provide的同步更新。
@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
仅 @Observed/@ObjectLink 可以观察嵌套场景,其他的状态变量仅能观察第一层
- @Observed装饰class,需要观察多层嵌套场景的class需要被@Observed装饰。单独使用@Observed没有任何作用,需要和@ObjectLink、@Prop连用。
- @ObjectLink装饰的变量接收@Observed装饰的class的实例,应用于观察多层嵌套场景,和父组件的数据源构建双向同步。
- @ObjectLink初始化规则
框架行为
- 初始渲染:
- @Observed装饰的class的实例会被不透明的代理对象包装,代理了class上的属性的setter和getter方法
- 子组件中@ObjectLink装饰的从父组件初始化,接收被@Observed装饰的class的实例,@ObjectLink的包装类会将自己注册给@Observed class。
- 属性更新:
- 当@Observed装饰的class属性改变时,会走到代理的setter和getter,然后遍历依赖它的@ObjectLink包装类,通知数据更新。
管理应用拥有的状态
LocalStorage:页面级UI状态存储
LocalStorage是ArkTS为构建页面级别状态变量提供存储的内存内“数据库”
- 应用程序可以创建多个LocalStorage实例
- LocalStorage是页面级的UI状态存储,通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例
- LocalStorage也可以通过GetShared接口,获取在UIAbility里创建的GetShared,实现跨页面、UIAbility内共享
@LocalStorageProp
- @LocalStorageProp装饰的变量和与LocalStorage中给定属性建立单向同步关系
- @LocalStorageProp初始化规则如下。它从LocalStage中取key对应的值,不存在的话就是用本地的默认值,同时它也可以被提供给子组件初始化状态变量
框架行为
- 当@LocalStorageProp(key)装饰的数值改变被观察到时,修改不会被同步回LocalStorage对应属性键值key的属性中。
- 当前@LocalStorageProp(key)单向绑定的数据会被修改,即仅限于当前组件的私有成员变量改变,其他的绑定该key的数据不会同步改变。
- 当@LocalStorageProp(key)装饰的数据本身是状态变量,它的改变虽然不会同步回LocalStorage中,但是会引起所属的自定义组件的重新渲染。
- 当LocalStorage中key对应的属性发生改变时,会同步给所有@LocalStorageProp(key)装饰的数据,@LocalStorageProp(key)本地的修改将被覆盖。
@LocalStorageLink
- @LocalStorageLink装饰的变量和在@Component中创建与LocalStorage中给定属性建立双向同步关系。
- 它的初始化规则和@LocalStorageProp一样
框架行为
- 当@LocalStorageLink(key)装饰的数值改变被观察到时,修改将被同步回LocalStorage对应属性键值key的属性中。
- LocalStorage中属性键值key对应的数据一旦改变,属性键值key绑定的所有的数据(包括双向@LocalStorageLink和单向@LocalStorageProp)都将同步修改;
- 当@LocalStorageLink(key)装饰的数据本身是状态变量,它的改变不仅仅会同步回LocalStorage中,还会引起所属的自定义组件的重新渲染。
AppStorage:应用全局的UI状态存储
- LocalStorage是页面级的,通常应用于页面内的数据共享
- AppStorage是一个特殊的单例LocalStorage对象,是应用级的数据库,和进程绑定,通过 @StorageProp 装饰器可以和组件联动
- AppStorage还相当于整个应用的“中枢”, 持久化数据PersistentStorage 和 环境变量Environment 都是通过和AppStorage中转,才可以和UI交互
- @StorageProp的初始化规则如下。和@LocalStorageProp相似,只是取值的对象不同
@StorageProp 的框架行为
- 当@StorageProp(key)装饰的数值改变被观察到时,修改不会被同步回AppStorage对应属性键值key的属性中。
- 当前@StorageProp(key)单向绑定的数据会被修改,即仅限于当前组件的私有成员变量改变,其他的绑定该key的数据不会同步改变。
- 当@StorageProp(key)装饰的数据本身是状态变量,它的改变虽然不会同步回AppStorage中,但是会引起所属的自定义组件的重新渲染。
- 当AppStorage中key对应的属性发生改变时,会同步给所有@StorageProp(key)装饰的数据,@StorageProp(key)本地的修改将被覆盖
- @StorageLink的初始化规则如下。和@LocalStorageLink相似,只是取值的对象不同
@StorageLink 的框架行为
- 当@StorageLink(key)装饰的数值改变被观察到时,修改将被同步回AppStorage对应属性键值key的属性中。
- AppStorage中属性键值key对应的数据一旦改变,属性键值key绑定的所有的数据(包括双向@StorageLink和单向@StorageProp)都将同步修改;
- 当@StorageLink(key)装饰的数据本身是状态变量,它的改变不仅仅会同步回AppStorage中,还会引起所属的自定义组件的重新渲染。
PersistentStorage:持久化存储UI状态
- LocalStorage和AppStorage都是运行时的内存
- PersistentStorage将选定的AppStorage属性保留在设备磁盘上
- 应用程序通过API,以决定哪些AppStorage属性应借助PersistentStorage持久化
- UI和业务逻辑不直接访问PersistentStorage中的属性,所有属性访问都是对AppStorage的访问,AppStorage中的更改会自动同步到PersistentStorage
- PersistentStorage的持久化变量最好是小于2kb的数据,不要大量的数据持久化,因为PersistentStorage写入磁盘的操作是同步的
- 通过 PersistentStorage.PersistProp(‘aProp’, 47) 指定需要持久化的数据。它的工作过程如下
- 触发点击事件后:
- 状态变量@StorageLink(‘aProp’) aProp改变,触发Text组件重新刷新。
- @StorageLink装饰的变量是和AppStorage中建立双向同步的,所以@StorageLink(‘aProp’) aProp的变化会被同步回AppStorage中。
- AppStorage中“aProp”属性的改变会同步到所有绑定该“aProp”的单向或者双向变量,在本示例中没有其他的绑定“aProp”的变量。
- 因为“aProp”对应的属性已经被持久化,所以在AppStorage中“aProp”的改变会触发PersistentStorage将新的改变写入本地磁盘。
- 后续启动应用:
- 执行PersistentStorage.PersistProp(‘aProp’, 47),在首先查询在PersistentStorage本地文件查询“aProp”属性,成功查询到。
- 将在PersistentStorage查询到的值写入AppStorage中。
- 在Index组件里,@StorageLink绑定的“aProp”为PersistentStorage写入AppStorage中的值,即为上一次退出引用存入的值。
Environment:设备环境查询
- Environment是ArkUI框架在应用程序启动时创建的单例对象。它为AppStorage提供了一系列描述应用程序运行状态的属性
- Environment的所有属性都是不可变的(即应用不可写入),所有的属性都是简单类型
- 设备环境到Component的更新链:Environment --> AppStorage -->Component
其他状态管理功能
@Watch装饰器:状态变量更改通知
- @Watch用于监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用
- @Watch在ArkUI框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当在严格相等为false的情况下,就会触发@Watch的回调
框架行为
- 当观察到状态变量的变化(包括双向绑定的AppStorage和LocalStorage中对应的key发生的变化)的时候,对应的@Watch的回调方法将被触发;
- @Watch方法在自定义组件的属性变更之后同步执行;
- 如果在@Watch的方法里改变了其他的状态变量,也会引起状态变更和@Watch的执行;
- 在第一次初始化的时候,@Watch装饰的方法不会被调用,即认为初始化不是状态变量的改变。只有在后续状态改变时,才会调用@Watch回调方法
$$
语法:内置组件双向同步
- 当前
$$
支持基础类型变量,以及@State、@Link和@Prop装饰的变量。 - 当前
$$
仅支持 Refresh 组件的refreshing参数。 $$
绑定的变量变化时,会触发UI的同步刷新$$
还可以用作 @Builder的状态更新
渲染控制
大家直接跳到链接去看吧。里面内容很简单也很容易理解
为了能让大家更好的学习鸿蒙 (Harmony OS) 开发技术,这边特意整理了《鸿蒙 (Harmony OS)开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05
《鸿蒙 (Harmony OS)开发学习手册》
入门必看:https://qr21.cn/FV7h05
- 应用开发导读(ArkTS)
- 应用开发导读(Java)
HarmonyOS 概念:https://qr21.cn/FV7h05
- 系统定义
- 技术架构
- 技术特性
- 系统安全
如何快速入门:https://qr21.cn/FV7h05
- 基本概念
- 构建第一个ArkTS应用
- 构建第一个JS应用
- ……
开发基础知识:https://qr21.cn/FV7h05
- 应用基础知识
- 配置文件
- 应用数据管理
- 应用安全管理
- 应用隐私保护
- 三方应用调用管控机制
- 资源分类与访问
- 学习ArkTS语言
- ……
基于ArkTS 开发:https://qr21.cn/FV7h05
- Ability开发
- UI开发
- 公共事件与通知
- 窗口管理
- 媒体
- 安全
- 网络与链接
- 电话服务
- 数据管理
- 后台任务(Background Task)管理
- 设备管理
- 设备使用信息统计
- DFX
- 国际化开发
- 折叠屏系列
- ……