1、@Local组件内部状态
@Local装饰的变量只能在组件内部初始化@Local只能装饰变量本身- 对于简单类型
string、number、boolean可以观察到赋值变化 - 对于对象,监听到整体赋值,无法监听属性变化(如需要监听属性,请使用
ObservedV2 + @Trace) - 对于
Array,监听到整体赋值与数组元素项的变化 - 其他类型:
Date、Map、Set、Array可以观察到整体赋值和API调用引起的变化
- 对于简单类型
与V1的@State对比
+ `@State`允许从外部初始化,也可以本地初始化,`@Local`仅允许本地初始化
+ `@State`对于对象类型可以观察到整体赋值与第一层属性变化,`@Local`对于对象类型仅能观察到赋值(深层监听依赖`ObservedV2 + @Trace`)
// 1、基本数据类型均可观察变化
@Local age: number = 18;
// 2、类对象类型观察到整体赋值,无法观察到对象属性变化
@Local person: Person = new Person('小玉', 18);
// 3、Array类型观察到整体赋值,也可以观察到元素项变化
@Local arr: string[] = ['a'];
// 4、Map、Date、Set等内置对象观察到整体赋值和由于API调用引起的变化,如:Map.set() Set.add() Date.setFullYear()
@Local mapping: Map<string, string> = new Map();
// 5、Array嵌套复杂类型时,可观察到Array的整体赋值,元素项的监听同上,如元素项是类对象则对应第2条规则
@Local list: Person[] = [new Person('', 0)];
2、@Param组件外部输入 @Once初始化同步一次
@Param可以外部初始化,也支持本地初始化,当同时存在本地初始值与外部传入值时,会优先使用外部传入值进行初始化@Param @Require搭配使用表示必须从外部传入初始化@Param @Once搭配使用表示仅从外部初始化一次、不接受后续同步变化的能力,并且此时允许组件内部修改变量
@Param装饰的变量不允许组件内部修改,只能等数据源(必须为状态变量才能同步)变化时同步给子组件- 对于复杂类型,
@Param接受数据源的引用(但此时组件内部对类对象属性的修改会同步给数据源)
与V1的@Prop对比
+ `@Prop`对于复杂类型会深拷贝,性能差,`@Param`接受数据源的引用
// 1、可以外部初始化,也可以本地初始化,同时存在则使用外部
@Param name: string = '';
// 2、与@Require搭配使用表示必须由外部初始化
@Param @Require age: number;
// 3、与@Once搭配使用表示仅从外部初始化一次、不接受后续同步变化的能力,此时变量可以在组件内部修改
@Param @Once msg: string = '';
3、@Event规范组件输出
@Event主要配合@Param实现数据的父子同步,由于@Param在组件本地无法修改,需要父组件通过自定义方法(修改状态变量的方法)传入到子组件,子组件内部调用触发,父组件再同步回子组件实现双向同步
// Child.ets
// @Param标志着组件的输入
@Param msg: string = '';
// @Event标志着组件的输出,可以通过该方法影响父组件
@Event changeMsg: () => void = () => {};
// Father
@Local msg: string = 'Hello World';
changeMsg() {
this.msg = 'Hello ArkTS'
}
build() {
// changeMsg通过自定义方法
Child({ msg: this.msg, changeMsg: () => { this.changeMsg() } })
}
4、@ObservedV2类对象的深度监听 @Trace与按需监听
@ObservedV2装饰器仅能装饰class与@Trace装饰器仅能装饰类的成员属性,需要配合使用,单独使用不起作用- 只有被
@Trace装饰的类成员属性变更时,才会通知依赖该属性的UI更新 - 多层嵌套类场景时,仅被直接使用的属性需要被
@Trace装饰且该属性所属类需要被@ObservedV2装饰
与V1的@Observed & @ObjectLink对比
+ `V1 @Observed`搭配`@Track`使用,但是需要使用`@ObjectLink`接收`@Observed`装饰的类的实例,这需要额外创建组件
@ObservedV2
class Info {
// 只需要被直接使用的属性@Trace装饰,所属类被@ObservedV2装饰
@Trace num: number = 1
}
class Person {
name: string;
age: number;
info: Info;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
this.info = new Info()
}
}
@ComponentV2
struct StateV2 {
@Local person: Person = new Person('', 18);
build() {
Column({ space: 10 }) {
Button(`${this.person.info.num}`)
.onClick(() => {
this.person.info.num += 1;
})
}
}
}
// V1 针对多层嵌套类场景,@ObjectLink接收被@Observed需要单独创建组件
@Component
struct StateV1 {
@State person: Person = new Person('', 18);
build() {
Column({ space: 10 }) {
// 为了接收@Observed装饰的类的实例,需要额外创建一个组件
ChildComponent({ info: this.person.info })
}
}
}
@Component
struct ChildComponent {
@ObjectLink info: Info;
build() {
Button(`${this.info.num}`)
.onClick(() => {
this.info.num += 1;
})
}
}
5、@Monitor监听状态变量修改
- 仅能监听状态变量,无法监听普通变量
- 可一次性监听多个属性
- 具有深度监听的能力,可监听嵌套类、多维数组、对象数组中指定项的变化
与V1的@Watch对比
@Watch仅能监听单个数据,@Monitor可以多数据监听@Watch不能获取变化前的值,@Monitor可以获取变化前的值@Monitor可以整体监听,也可以监听被@Trace装饰的类成员属性,@Watch只能整体监听@Monitor支持监听多层嵌套场景,@Watch针对类对象状态变量的整体赋值与第一层属性变化@Monitor可以在@ObservedV2装饰的类中使用,@Watch不能
@ObservedV2
class Info {
@Trace weight: number = 90;
}
@ObservedV2
class Person {
// age被@Trace装饰,能够监听变化
@Trace age: number = 18;
// name未被@Trace装饰,不能够监听变化
name: string = '';
info: Info = new Info();
// 1、@Monitor可以在@ObservedV2装饰的类中使用
@Monitor('age')
ageChange(monitor: IMonitor) {}
// 2、@Monitor可以监听深层属性的变化,该深层属性需要被@Trace装饰
@Monitor('info.weight')
weightChange(monitor: IMonitor) {}
}
@ComponentV2
export struct StateV2 {
@Local num: number = 1;
@Local person: Person = new Person('', 18);
@Local arr: Person[][] = [[new Person('', 0), new Person('', 1)], []]
// 支持多属性监听、深度监听,类对象属性需要被@Trace装饰
@Monitor('num', 'person.info.weight', 'arr.0', 'arr.0.1.info.weight')
ageChange(monitor: IMonitor) {
monitor.dirty.forEach((path: string) => {
hilog.info(0x0123, '', `属性 ${path}:变化前 = ${monitor.value(path)?.before};变化后 = ${monitor.value(path)?.now}`)
})
}
build() {}
}
6、@Computed计算属性
- 方法装饰器,仅能装饰
getter方法,惰性计算,仅依赖属性变化时重新求值 - 可以在
@ObservedV2装饰的类中使用@Computed
// 1、在组件中使用
@ComponentV2
export struct StateV2 {
@Local num: number = 1;
@Local person: Person = new Person('张三', 18);
@Computed
get valName() {
return `${this.person.name} => ${this.num}`
}
}
// 2、在@ObservedV2装饰的类中使用@Computed
@ObservedV2
class Person {
@Trace name: string;
@Trace age: number;
info: Info;
constructor(name: string, age: number) {}
@Computed
get allMsg() {
return `${this.name}:年龄${this.age},体重${this.info.weight}`
}
}
7、AppStorageV2应用全局UI状态管理
connect(type, key?, defaultCreator)创建或获取储存的数据,key未指定时,则使用type的name作为keyremove(keyOrType)删除指定key的储存数据,如果参数为type则使用type的name作为keykeys()返回所有AppStorageV2中的key
@ObservedV2
class SafeArea {
@Trace top: number;
@Trace bottom: number;
constructor(top: number, bottom: number) {
this.top = top;
this.bottom = bottom;
}
}
export default class EntryAbility extends UIAbility {
onCreate() {
// AppStorageV2是应用级别的,可以在全局任意地方使用
AppStorageV2.connect(SafeArea, () => new SafeArea(top.topRect.height, bottom.bottomRect.height))
}
}
@ComponentV2
export struct StateV2 {
// connect仅一个参数type时,需确保有值否则程序奔溃crash
@Local area: SafeArea = AppStorageV2.connect(SafeArea)!;
build() {
Colume() {
Text(`${this.area.top}`)
// 更新数据
.onClick(() => {
// 直接修改通过 AppStorageV2.connect 获取的对象属性即可。修改后的数据会自动同步到 AppStorageV2 中
this.area.top += 1;
})
}
}
}
V2 中不存在也不需要页面级的状态管理 LocalStorage,使用@ObservedV2/@Trace实现相同效果
- 声明
@ObservedV2装饰的类 - 声明被
@Trace的属性作为页面间共享的可观察的数据 - 状态管理
V2将观察能力增强到数据本身,数据本身就是可观察的,更改数据会触发相应的视图的更新,因此只需要对同一个类实例进行修改会触发所有引用该实例的UI刷新
// Constant.ets
// 写法1,单例
@ObservedV2
export class BaseInfo {
@Trace num: number = 1;
static singleton_: BaseInfo;
static instance() {
if (!BaseInfo.singleton_) {
BaseInfo.singleton_ = new BaseInfo();
}
return BaseInfo.singleton_;
}
}
// 写法2,直接导出实例
@ObservedV2
export class UserInfo {
@Trace age: number = 1
}
export const userInfo: UserInfo = new UserInfo();
// 此时数据在AB页面之间共享
// PageA.ets
import { BaseInfo, userInfo, UserInfo } from './Constant.ets'
@ComponentV2
export struct PageA {
baseInfo: BaseInfo = BaseInfo.instance();
@Local userInfo: UserInfo = userInfo;
build() {
Column() {
Button('click').onClick(() => {
this.baseInfo.num += 1;
})
Button(`${this.userInfo.age}`).onClick(() => {
// 页面A的修改会同步给B
this.userInfo.age += 1;
})
}
}
}
// PageB.ets
import { BaseInfo, userInfo, UserInfo } from './Constant.ets'
export struct PageB {
baseInfo: BaseInfo = BaseInfo.instance();
@Local userInfo: UserInfo = userInfo;
build() {
Column() {
Button('click').onClick(() => {
this.baseInfo.num += 1;
})
Button(`${this.userInfo.age}`).onClick(() => {
// 页面B的修改会同步给A
this.userInfo.age += 1;
})
}
}
}
8、@Provider 和 @Consumer 跨组件层级双向同步
@Provider(aliasName?: string) varName: varType = initValue; @Consumer(aliasName?: string) varName: varType = defaultValue;
aliasName别名,缺省时使用属性名varName- 必须
本地初始化,区别:V1(@Provide)允许从父组件初始化 - 可以装饰
箭头函数,区别:V1(@Provide)不支持 - 装饰复杂类型时依赖
@ObservedV2和@Trace
@ObservedV2
class Person {
@Trace age: number = 18;
}
// 父组件
@Provider() appName: string = '鸿蒙App';
@Provider('aliasName') varName: number = 1;
// 装饰箭头函数
@Provider() onDrag: (x: number, y: number) => void = (x: number, y: number) => {
hilog.info(0x0123, '', `${x} => ${y}`)
}
// 复杂类型
@Provider('person') p: Person = new Person();
// 子孙组件
@Consumer() appName: string = ''; // 缺省别名则使用属性名向上查找最近的同名属性
@Consumer('aliasName') localName: string = '';
@Consumer() onDrag: (x: number, y: number) => void = () => {};
@Consumer() person: Person | undefined;
9、PersistenceV2 持久化存储状态
-
PersistenceV2是在应用UI启动时会被创建的单例,会将最新数据储存在设备磁盘上(持久化) -
持久化的数据必须是
class对象,不能是容器(如Array、Set、Map),不能是内置的构造对象(如Date、Number) -
单个
key支持数据大小约8k,过大会导致持久化失败 -
持久化存储
@ObservedV2 & @Trace的数据改变会触发自动持久化,其他数据需要手动触发持久化 -
connect(type, keyOrDefaultCreater?, defaultCreator?) 创建或获取储存的数据
- 未指定
key则使用type.name作为key,此时第二个参数指默认构造器
- 未指定
-
remove(keyOrType) 删除指定
key的储存数据 -
keys() 返回所有
PersistenceV2中的key -
save(keyOrType) 手动持久化数据
-
notifyOnError(callback) 响应序列化或反序列化失败的回调
- 将数据存入磁盘时,需要对数据进行序列化;当某个key序列化失败时,错误是不可预知的;可调用该接口捕获异常
@ObservedV2
class Result {
code: number = 0;
@Trace msg: string = '成功';
}
// 创建或获取储存的数据
@Local main: Result = PersistenceV2.connect(Result, 'Result', () => new Result())!;
msgChange() {
// @ObservedV2 & @Trace装饰的数据会自动持久化
this.main.msg = '失败';
}
codeChange() {
this.main.code = -1;
// 未被@Trace装饰的成员如果需要进行持久化保存需要手动触发 save
PersistenceV2.save('Result');
}
10、!!语法糖 双向绑定
@Event方法名需要申明为'$' + @Param属性名
Child({ value: this.value!! }) // 语法糖,相当于 Child({ value: this.value, $value: (val: string) => { this.value = val; } })
// Child
@Param value: string = '';
@Event $value: (val: string) => void = () => {};
handleChange() {
this.$value('xxx');
}
11、makeObserved 将非观察数据变为可观察数据
makeObserved可以在@Trace无法标记的情况下使用class的定义在三方包中interface或者JSON.parse返回的匿名对象
- 仅支持非空的对象类型传参
makeObserved不支持传入被@ObservedV2、@Observed装饰的类的实例以及已经被makeObserved封装过的代理数据。为了防止双重代理,makeObserved发现入参为上述情况时则直接返回,不做处理makeObserved可以使用在V1 @Component中,但不能搭配装饰器使用
class NavConfig {
navName: string
constructor(name: string) {
this.navName = name
}
}
@ComponentV2
struct Index {
// 将非观察数据JSON.parse()返回的匿名对象变为可观察数据,不需要装饰器修饰
navConfig: NavConfig = UIUtils.makeObserved(JSON.parse(JSON.stringify(new NavConfig('Home'))));
// makeObserved可以和V2的装饰器一起使用,使对象具有深度观察能力
@Local navConfig: NavConfig = UIUtils.makeObserved(new NavConfig('Home'));
handleChange() {
// 会触发UI更新
this.navConfig.navName = 'List';
}
}
// makeObserved可以使用在@Component中使用,使数据具有观察能力
@Component
struct Index {
// 数据已具有观察能力,可以触发UI刷新
message: Info = UIUtils.makeObserved(new Info(20));
}
12、UIUtils.getTarget 获取状态管理框架代理前的原始对象
class Person {
age = 1;
}
const person = new Person();
@ComponentV2
struct Index {
private p: Person = UIUtils.makeObserved(person);
aboutToAppear() {
// 1、getTarget仅支持对象类型传参
UIUtils.getTarget(1); // Argument of type 'number' is not assignable to parameter of type 'object'. <ArkTSCheck>
const temp = UIUtils.getTarget(this.p); // 获取代理前的原始对象
temp === person; // true
// 2、更改getTarget获取的原始对象中的内容不会被观察到变化,也不会触发UI刷新
temp.age += 1;
}
}