关键字、操作符

关键字

??

data??''表示如果datanullundefined否则返回右侧的''

?.

data?.表达式表示如果data对象为nullundefined,返回undefined,不会抛出异常【操作的是对象及其属性或方法;返回的是属性值或方法返回值,如果对象不存在则返回undefined;用于安全访问】

?:

表达式?:表达式【操作的是条件和表达式;返回的是满足条件的表达式;用于条件判断】

=> (又名Lambda函数)

${变量} 字符串模版字面量

Promise

异步并发概述 (Promise和async/await)

Promise有三种状态:pending(进行中)、fulfilled(已完成)、rejected(已拒绝)。Promise对象创建后处于pending状态,并在异步操作完成后转换为fulfilledrejected状态。

单次I/O任务开发指导

async

await

let 定义变量

const 定义常量

concat

把指定字符串拼接到前面字符串的最后

UI范式基本语法

@Builder

  1. 组件内的@Builder方法可通过this访问当前组件的属性和方法,而全局的@Builder方法则不能
  2. 组件内的@Builder方法只能用于当前组件,全局的@Builder方法导出(export)后,可用于整个应用。
  3. @Builder方法具有两种参数传递机制——按值传递按引用传递。当只有一个参数且参数为对象字面量时为按引用传递,其余情况均为按值传递。
    按引用传递时,若传递的参数为状态变量,则状态变量的变化将会触发@Builder方法内部UI的刷新;按值传递时则不会。

    @Builder方法和自定义组件的区别

    @Builder方法和自定义组件虽然都可以实现UI复用的效果,但是两者还是有着本质的区别的,其中最为显著的一个区别就是自定义组件可以定义自己的状态变量,而@Builder方法则不能。

    [!总结] 若复用的UI结构没有状态,推荐使用@Builder方法,否则使用自定义组件。

    @LocalBuilder装饰器: 维持组件父子关系

    @BuilderParam装饰器:引用@Builder函数

    wrapBuilder:封装全局@Builder

    @Styles装饰器:定义组件重用样式

    @Extend装饰器:定义扩展组件样式

    stateStyles:多态样式

    @AnimatableExtend装饰器:定义可动画属性

    @Require

    校验@Prop@State@Provide@BuilderParam普通变量(无状态装饰器修饰的变量)是否需要构造传参的一个装饰器。
    添加了@Require就是必传的参数,不传编译不通过。

    @Reusable装饰器:组件复用

状态管理V1

管理组件拥有的状态

@Component自定义组件

  1. @BuilderParam用于装饰自定义组件(struct)中的属性,其装饰的属性可作为一个UI结构的==占位符==,待创建该组件时,可通过参数为其传入具体的内容。(其作用类似于Vue框架中的slot)。
  2. 一个组件中只定义了一个@BuilderParam属性,那么创建该组件时,也可直接通过”子组件“(可用@Builder构建的组件当子组件)的方式传入具体的UI结构

@State装饰器:组件内状态

@State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。
在状态变量相关装饰器中,@State是最基础的,使变量拥有状态属性的装饰器,它也是大部分状态变量的数据源。
@State装饰的变量,与声明式范式中的其他被装饰变量一样,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化。初始化也可选择使用命名参数机制从父组件完成初始化。
@State装饰的变量拥有以下特点:

  • @State装饰的变量与子组件中的@Prop装饰变量之间建立单向数据同步,与@Link@ObjectLink装饰变量之间建立双向数据同步
  • @State装饰的变量生命周期与其所属自定义组件的生命周期相同

鸿蒙中如何实现UI自动刷新的

本质是通过回调
每个被@State修饰的变量最终都会被编译成一个ObservedPropertyObjectPU类的实现(/state_mgmt/src/lib/common/ObservedPropertyObjectPU是继承了ObservedPropertyPU的一个实现,前者的所有set/get方法都会调用后者的set/get方法)。
ArkTS在TS基础上把我们用状态修饰器修饰的变量进行了封装,内部通过ObservedPropertyPU类中set/get来更新值

fold title:@State自动刷新的关键源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
ObservedPropertyPU 类中  

set方法
public set(newValue: T): void {
如果两者是同一个变量,用=== 判断,则直接return不进行刷新,本次是无效刷新
if (this.wrappedValue_ === newValue) {
stateMgmtConsole.debug(`ObservedPropertyObjectPU[${this.id__()}, '${this.info() || "unknown"}']: set with unchanged value - ignoring.`);
return;
}
stateMgmtConsole.propertyAccess(`${this.debugInfo()}: set: value about to changed.`);
把旧的,也就是上一个值用oldValue变量记录,方便后续进行UI刷新的判断。
const oldValue = this.wrappedValue_;

setValueInternal方法中会把this.wrappedValue_ 更新为newValue
if (this.setValueInternal(newValue)) {
TrackedObject.notifyObjectValueAssignment(/* old value */ oldValue, /* new value */ this.wrappedValue_,
这里触发了UI刷新,在鸿蒙api 9 的版本只会走notifyPropertyHasChangedPU里面的内容刷新,这里大家可以思考一下
this.notifyPropertyHasChangedPU.bind(this),
this.notifyTrackedObjectPropertyHasChanged.bind(this));
}
}

状态复制管理
private setValueInternal(newValue: T): boolean {
stateMgmtProfiler.begin("ObservedPropertyPU.setValueInternal");
if (newValue === this.wrappedValue_) {
stateMgmtConsole.debug(`ObservedPropertyObjectPU[${this.id__()}, '${this.info() || "unknown"}'] newValue unchanged`);
stateMgmtProfiler.end();
return false;
}

if (!this.checkIsSupportedValue(newValue)) {
stateMgmtProfiler.end();
return false;
}

// 解除旧的绑定
this.unsubscribeWrappedObject();
if (!newValue || typeof newValue !== 'object') {
// undefined, null, simple type:
// nothing to subscribe to in case of new value undefined || null || simple type
this.wrappedValue_ = newValue;
} else if (newValue instanceof SubscribableAbstract) {
stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an SubscribableAbstract, subscribing to it.`);
this.wrappedValue_ = newValue;
(this.wrappedValue_ as unknown as SubscribableAbstract).addOwningProperty(this);
} else if (ObservedObject.IsObservedObject(newValue)) {
stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an ObservedObject already`);
ObservedObject.addOwningProperty(newValue, this);
this.shouldInstallTrackedObjectReadCb = TrackedObject.needsPropertyReadCb(newValue);
this.wrappedValue_ = newValue;
} else {
stateMgmtConsole.propertyAccess(`${this.debugInfo()}: setValueInternal: new value is an Object, needs to be wrapped in an ObservedObject.`);
this.wrappedValue_ = ObservedObject.createNew(newValue, this);
this.shouldInstallTrackedObjectReadCb = TrackedObject.needsPropertyReadCb(this.wrappedValue_);
}
stateMgmtProfiler.end();
return true;
}

这里面实际主要做了以下三件事:

  1. 进行内部状态值更新,并设置回调。【setValueInternal里面会把wrappedValue_更新为最后一次set的值】
  2. 绑定回调方,比如当属性发生通知的时候,通过回调告诉回调方。【绑定回调方。这里先通过this.unsubscribeWrappedObject(); 把旧的值解除绑定。这里面判断了是SubscribableAbstract还是ObservedObject进行单独的处理。】
fold
1
2
3
4
5
6
7
8
9
10
11
12
13
private unsubscribeWrappedObject() {  
if (this.wrappedValue_) {
if (this.wrappedValue_ instanceof SubscribableAbstract) {
(this.wrappedValue_ as SubscribableAbstract).removeOwningProperty(this);
} else {
ObservedObject.removeOwningProperty(this.wrappedValue_, this);

// make sure the ObservedObject no longer has a read callback function
// assigned to it
ObservedObject.unregisterPropertyReadCb(this.wrappedValue_);
}
}
}
  1. 把UI设置为脏处理,应用于后面UI的刷新流程。【setValueInternal返回true的时候,就会执行notifyObjectValueAssignment进行回调,最终分为两个分支:如果class里面没有@Track装饰器修饰的变量,则通过notifyPropertyHasChangedPU方法进行刷新(所有依赖这个class的UI都会被刷新),如果有的话,则通过notifyTrackedObjectPropertyHasChanged进行刷新(只依赖@Track装饰器修饰的变量的UI才会刷新)。】
1
2
3
4
5
if (this.setValueInternal(newValue)) {
TrackedObject.notifyObjectValueAssignment(/* old value */ oldValue, /* new value */ this.wrappedValue_,
this.notifyPropertyHasChangedPU.bind(this),
this.notifyTrackedObjectPropertyHasChanged.bind(this));
}

@Prop装饰器:父子单向同步

@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。
@Prop自带@State效果,即更改@Prop修饰的变量会更新UI【子组件的aboutToApear()在创建时自动触发】
@Prop装饰的变量和父组件建立单向的同步关系:

  • @Prop变量允许在本地修改,但修改后的变化不会同步回父组件。
  • 当父组件中的数据源更改时,与之相关的@Prop装饰的变量都会自动更新。如果子组件已经在本地修改了@Prop装饰的相关变量值,而在父组件中对应的@State装饰的变量被修改后,子组件本地修改的@Prop装饰的相关变量值将被覆盖。

    @Link装饰器:父子双向同步

    子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。
    @Link装饰的变量与其父组件中的数据源共享相同的值。
    @Link装饰器不能在@Entry装饰的自定义组件中使用。

    @Provide装饰器/@Consume装饰器

    @Provide@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide@Consume摆脱参数传递机制的束缚,实现跨层级传递
    其中@Provide装饰的变量是在祖先节点中,可以理解为被“提供”给后代的状态变量。@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先节点提供的变量。
    @Provide/@Consume装饰的状态变量有以下特性:
  • @Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
  • 后代通过使用@Consume去获取@Provide提供的变量,建立在@Provide@Consume之间的双向数据同步,与@State/@Link不同的是,前者可以在多层级的父子组件之间传递。
  • @Provide@Consume可以通过相同的变量名或者相同的变量别名绑定,变量类型必须相同。
    1
    2
    3
    4
    5
    6
    7
    // 通过相同的变量名绑定
    @Provide a: number = 0;
    @Consume a: number;

    // 通过相同的变量别名绑定
    @Provide('a') b: number = 0;
    @Consume('a') c: number;
    @Provide@Consume通过相同的变量名或者相同的变量别名绑定时,@Provide修饰的变量和@Consume修饰的变量是一对多的关系。不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的@Provide装饰的变量。

    [!tip]
    @Provide 可以和 @Watch 一起用,和其他状态变量不能一起用(编译报错)

    @Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化

    @Observed和@ObjectLink嵌套对象属性更改UI不刷新问题

    使用了@Observed@ObjectLink,修改嵌套对象的属性,UI还是不刷新,常见问题有三种形式:

  1. 多级嵌套,嵌套对象的并没有添加@Observed进行监听
  2. 多级嵌套,嵌套对象的 View 组件没有抽离出来,添加@ObjectLink进行该级对象的监听绑定
  3. 嵌套对象,并没有new出来创建,直接赋值没有创建对象的过程,无法激活Observed监听

    管理应用拥有的状态

    @AppStorage

    @AppStorage应用全局的UI状态存储
    应用全局的UI状态存储,和应用进程绑定。是“中枢”,持久化数据PersistentStorage环境变量Environment都是通过它来中转,才可与UI交互。【单例、它的所有API都是静态的】
  • 修饰器@StorageProp 单向数据同步,AppStorage改变可同步给@StorageProp并覆盖本地的修改
    @StorageProp初始化规则图示
  • 修饰器@StorageLink 双向数据同步
    @StorageLink初始化规则图示
    @StorageProp

LocalStorage 页面级UI状态存储

@LocalStorageProp

PersistentStorage:持久化存储UI状态

Environment:设备环境查询

其他状态管理

@Watch

状态变量更改通知。(===)严格相等为false时触发@Watch的回调
1. 当观察到状态变量的变化(包括双向绑定的AppStorageLocalStorage中对应的key发生的变化)的时候,对应的@Watch的回调方法将被触发;
2. @Watch方法在自定义组件的属性变更之后同步执行;
3. 如果在@Watch的方法里改变了其他的状态变量,也会引起状态变更和@Watch的执行;
4. 在第一次初始化的时候,@Watch装饰的方法不会被调用,即认为初始化不是状态变量的改变。只有在后续状态改变时,才会调用@Watch回调方法。

$$语法:内置组件双向同步

  • $$绑定的变量变化时,会触发UI的同步刷新。
  • 当前$$支持基础类型变量,以及@State@Link@Prop装饰的变量
  • 当前$$支持的组件:
组件 支持的参数/属性 起始API版本
Checkbox提供多选框组件 select 10
CheckboxGroup selectAll 10
DatePicker日期选择器组件 selected 10
TimePicker时间选择器组件 selected 10
MenuItem展示菜单Menu中具体的item菜单项 selected 10
Panel可滑动面板【停止维护】 mode 10
Radio单选框 checked 10
Rating评分组件 rating 10
Search搜索框组件 value 10
SideBarContainer可以显示和隐藏的侧边栏容器 showSideBar 10
Slider滑动条组件 value 10
Stepper步骤导航器组件 index 10
Swiper滑块视图容器,提供子组件滑动轮播显示的能力 index 10
Tabs通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图 index 10
TextArea多行文本输入框组件,可自动换行 text 10
TextInput单行文本输入框组件 text 10
TextPicker滑动选择文本内容的组件 selected、value 10
Toggle组件提供勾选框样式、状态按钮样式及开关样式。 isOn 10
AlphabetIndexer可以与容器组件联动用于按逻辑结构快速定位容器显示区域的组件 selected 10
Select提供下拉选择菜单,可以让用户在多个选项之间选择。 selected、value 10
BindSheet半模态转场 isShow 10
BindContentCover# 全屏模态转场 isShow 10
Refresh页面下拉操作并显示刷新动效的容器组件 refreshing 8
GridItem网格容器中单项内容容器 selected 10
ListItem展示列表具体item,必须配合List来使用 selected 10

@Track装饰器:class对象属性级更新

应用于==class对象==的属性级更新。装饰的属性变化时,只会触发该属性关联的UI更新。

@Track变量装饰器 说明
装饰器参数
可装饰的变量 class对象的非静态成员属性。

自定义组件冻结功能

状态管理V2

@ComponentV2自定义组件

@ObservedV2装饰器和@Trace装饰器:类属性变化观测

@Local

@ComponentV2装饰的自定义组件中变量变化的观测,开发者可以使用@Local装饰器装饰变量。

router

页面路由 (@ohos.router)(不推荐)
Router模块通过不同的url地址,可以方便地进行页面路由。

  • 页面跳转【页面栈的最大容量为32个页面】

    • router.pushUrl():目标页面不会替换当前页,而是压入页面栈。这样可以保留当前页的状态,并且可以通过返回键或者调用router.back()方法返回到当前页。
    • router.replaceUrl():目标页面会替换当前页,并销毁当前页。这样可以释放当前页的资源,并且无法返回到当前页。
    • router.clear()方法清空历史页面栈
    • 两种实例模式,分别是Standard【默认】和Single
      • Standard:多实例模式【默认】。目标页面会被添加到页面**==栈==顶**,无论栈中是否存在相同url的页面。
      • Single:单实例模式。如果目标页面的url已经存在于页面栈中,则会将离栈顶最近的同url页面移动到栈顶【没有清空栈中该url以上的其他页面】,该页面成为新建页。如果目标页面的url在页面栈中不存在同url页面,则按照默认的多实例模式进行跳转。
  • 页面返回

    • 使用router.back()方法返回到指定页面时,原栈顶页面(包括)到指定页面(不包括)之间的所有页面栈都将从栈中弹出并销毁
    • 使用router.back()方法返回到原来的页面,原页面不会被重复创建,因此使用@State声明的变量不会重复声明,也不会触发页面的aboutToAppear()生命周期回调。如果需要在原页面中使用返回页面传递的自定义参数,可以在需要的位置进行参数解析。例如,在onPageShow()生命周期回调中进行参数解析。
  • 页面返回前增加一个询问框

  • 命名路由

    • 跳转到共享包Har或者Hsp中的页面(即共享包中路由跳转),可以使用router.pushNamedRoute()来实现
      • 给共享包的目标页面加别名 @Entry({routeName:'myPage'})
      • 当前包oh-package.json5dependencies中加"@ohos/library":"file:../library"
      • 当前包的当前页面加导入1. import '@ohos/library/src/main/ets/pages/Index'; // 引入共享包中的命名路由页面
      • 当前页面跳转使用 router.pushNamedRoute({ name: 'myPage', params:...})navigation
        navigation13
        Navigation组件是路由导航的根视图容器,一般作为Page页面的根容器使用,其内部默认包含了标题栏、内容区和工具栏,其中内容区默认首页显示导航内容(Navigation的子组件)或非首页显示(NavDestination13的子组件),首页和非首页通过路由进行切换。
        组件导航 (Navigation)(推荐)
        从API Version 9开始,推荐与 NavRouter 组件搭配使用。
        从API Version 10开始,推荐使用 NavPathStack 【路由栈信息】配合 NavDestination【NavRouter组件的子组件,用于显示导航内容区】属性进行页面路由。
        title:使用NavigationCustomTitle类型设置height高度时,titleMode属性不会生效。
  • 通用属性
    widthheightsizeSizeOptions对象】、paddingmarginlayoutWeightconstraintSizeConstraintSizeOptions对象】

    [!说明]

    1. 非全屏窗口下,Navigation/NavDestination设置的状态栏不生效
  • navigation加属性.mode(NavigationMode.Stack),Navigation组件即可设置为单页面显示模式。【默认为自适应模式】
    .mode(NavigationMode.Split),Navigation组件即可设置为分栏显示模式。

  • navigation路由相关操作(页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等)都是基于页面栈 NavPathStack 提供的方法进行。每个Navigation都需要创建并传入一个NavPathStack对象。

  • API version12开始,页面栈允许被继承。页面栈继承示例代码

  • pushPathByName:

除了支持通用属性外,Navigation 还支持以下属性:

  • title:设置页面标题。从 API version 11 开始,该接口支持在元服务中使用。
  • subTitle(deprecated):设置页面副标题。从 API Version 9 开始废弃,建议使用 title 代替。
  • menus:设置导航栏菜单项。
  • titleMode:设置标题栏模式。
  • toolBar(deprecated):设置工具栏。从 API Version 10 开始废弃,建议使用 toolbarConfiguration 代替。
  • toolbarConfiguration:设置工具栏配置。
  • hideToolBar:设置是否隐藏工具栏。
  • hideTitleBar:设置是否隐藏标题栏。
  • hideBackButton:设置是否隐藏返回按钮。
  • navBarWidth:设置导航栏宽度。
  • navBarPosition:设置导航栏位置。
  • mode:设置导航模式。
  • backButtonIcon:设置返回按钮图标。
  • hideNavBar:设置是否隐藏导航栏。
  • navDestination:设置导航目的地。
  • navBarWidthRange:设置导航栏宽度范围。
  • minContentWidth:设置内容区最小宽度。
  • ignoreLayoutSafeArea:设置是否忽略布局安全区域。
  • systemBarStyle:设置系统状态栏样式。

    事件

  • onTitleModeChange:标题栏模式改变事件。
  • onNavBarStateChange:导航栏状态改变事件。
  • onNavigationModeChange:导航模式改变事件。
  • customNavContentTransition:自定义导航内容过渡动画。

    相关类型

  • NavPathStack:路由栈信息。
  • NavPathInfo:路由路径信息。
  • PopInfo:弹出信息。
  • NavContentInfo:导航内容信息。
  • NavigationAnimatedTransition:导航动画过渡。
  • NavigationTransitionProxy:导航过渡代理。
  • NavigationInterception:导航拦截。
  • InterceptionShowCallback:导航显示拦截回调。
  • InterceptionModeCallback:导航模式拦截回调。
  • NavBar:导航栏。
  • NavigationMenuItem:导航菜单项。
  • ToolbarItem:工具栏项。
  • ToolbarItemStatus:工具栏项状态。

    枚举说明

  • NavigationTitleMode:标题栏模式。
  • NavigationCommonTitle:普通标题。
  • NavigationCustomTitle:自定义标题。
  • NavBarPosition:导航栏位置。
  • NavigationMode:导航模式。
  • TitleHeight:标题栏高度。
  • NavigationOperation:导航操作。
  • BarStyle:状态栏样式。
  • NavigationTitleOptions:标题栏选项。
  • NavigationToolbarOptions:工具栏选项。
  • LaunchMode:启动模式。
  • NavigationOptions:导航选项。

Preference

key为string非空长度超过1024个字节