Appearance
装饰器V2
@Track装饰器:class对象属性级更新
TIP
@Track应用于class对象的属性级更新。@Track装饰的属性变化时,只会触发该属性关联的UI更新
WARNING
使用场景:嵌套类、继承类、@Trace装饰基础类型数据、对象数据、Map类型、Set类型、Date类型
javascript
class CaseModelOne {
@Track str1: string = 'CaseModelOne_str1'
@Track str2: string = 'CaseModelOne_str2'
}
class CaseModelTwo {
str1: string = 'CaseModelTwo_str1'
str2: string = 'CaseModelTwo_str2'
}
/**
* CaseModelOne ------> 被@Track装饰的 对象的数据和状态装饰器绑定,修改对应的属性不会触发其他的组件刷新
*
* CaseModelTwo ------> 不被@Track装饰的 修改某一个对象属性值 会刷新整个使用CaseModelTwo下的属性值相关的组件
*
*
* 类LogTrack中的属性均被@Track装饰器装饰,点击按钮"change caseModelOne.str1",此时Text1刷新,Text2不刷新,只有一条日志输出,避免了冗余刷新。
* Text 1 is rendered
*
* 类logNotTrack中的属性均未被@Track装饰器装饰,点击按钮"change caseModelTwo.str1",此时Text3、Text4均会刷新,有两条日志输出,存在冗余刷新。
* Text 3 is rendered
* Text 4 is rendered
*/
@Entry
@ComponentV2
struct TrackCase {
@Local caseModelOne: CaseModelOne = new CaseModelOne()
@Local caseModelTwo: CaseModelTwo = new CaseModelTwo()
isRender(index: number) {
console.log(`Text ${index} is rendered`);
return ''
}
build() {
Row() {
Column() {
Text(this.caseModelOne.str1)
.fontWeight(FontWeight.Bold)
.fontSize(this.isRender(1))
Text(this.caseModelOne.str2)
.fontWeight(FontWeight.Bold)
.fontSize(this.isRender(2))
Button('change caseModelOne.str1')
.onClick(() => {
this.caseModelOne.str1 = 'caseModelOne'
})
Text(this.caseModelTwo.str1)
.fontWeight(FontWeight.Bold)
.fontSize(this.isRender(3))
Text(this.caseModelTwo.str2)
.fontWeight(FontWeight.Bold)
.fontSize(this.isRender(4))
Button('change caseModelTwo.str1')
.onClick(() => {
this.caseModelTwo.str1 = 'caseModelTwo'
})
}
.width('100%')
}
.height('100%')
}
}@ObservedV2装饰器和@Trace装饰器
TIP
@ObservedV2和@Trace提供了对嵌套类对象属性变化直接观测的能力,是状态管理V2中相对核心的能力之一
WARNING
使用场景:嵌套类、继承类、@Trace装饰基础类型数据、对象数据、Map类型、Set类型、Date类型
javascript
/** 嵌套类型*/
@ObservedV2
class CaseModelOne {
str1: CaseModelThree = new CaseModelThree()
}
/** 继承类型*/
@ObservedV2
class CaseModelTwo {
@Trace str1: string = 'CaseModelTwo_原始'
}
class CaseModelTwoSon extends CaseModelTwo{
getStr() {
this.str1 = 'CaseModelTwoSon_更新'
return this.str1
}
}
/** 基础类型*/
@ObservedV2
class CaseModelThree {
@Trace str1: string = 'CaseModelThree_原始'
}
/** 对象数组类型*/
@ObservedV2
class CaseModelFour {
@Trace str1?: string = 'CaseModelFour_原始'
constructor(name?: string) {
this.str1 = name
}
}
@ObservedV2
class CaseModelFourArray {
@Trace caseModelFourArray: CaseModelFour[] = [new CaseModelFour(), new CaseModelFour(), new CaseModelFour(), new CaseModelFour()]
}
/** Map类型*/
@ObservedV2
class CaseModelFive {
@Trace caseModelFiveMap: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]);
}
/** Set类型*/
@ObservedV2
class CaseModelSix {
@Trace caseModelSixSet: Set<string> = new Set(['0', '1', '2', '3'])
}
/** Date类型*/
@ObservedV2
class CaseModelSeven {
@Trace CaseModelSevenDate: Date = new Date('2021-08-08')
}
@Entry
@ComponentV2
struct ObservedV2AndTrace {
private caseModelOne: CaseModelOne = new CaseModelOne()
private caseModelTwoSon: CaseModelTwoSon = new CaseModelTwoSon()
private caseModelThree: CaseModelThree = new CaseModelThree()
private caseModelFourArray: CaseModelFourArray = new CaseModelFourArray()
private caseModelFive: CaseModelFive = new CaseModelFive()
private caseModelSix: CaseModelSix = new CaseModelSix()
private caseModelSeven: CaseModelSeven = new CaseModelSeven()
build() {
Row() {
Column() {
Text(this.caseModelOne.str1.str1)
.fontWeight(FontWeight.Bold)
Text(this.caseModelTwoSon.str1)
.fontWeight(FontWeight.Bold)
Text(this.caseModelThree.str1)
.fontWeight(FontWeight.Bold)
Text(this.caseModelFourArray.caseModelFourArray[0].str1)
.fontWeight(FontWeight.Bold)
Text(this.caseModelFive.caseModelFiveMap[0])
.fontWeight(FontWeight.Bold)
Text(`${this.caseModelSix.caseModelSixSet.values()}`)
.fontWeight(FontWeight.Bold)
Text(`${this.caseModelSeven.CaseModelSevenDate}`)
.fontWeight(FontWeight.Bold)
Button('change all value')
.onClick(() => {
this.caseModelOne.str1.str1 = 'caseModelOne_更新'
this.caseModelTwoSon.getStr()
this.caseModelThree.str1 = 'caseModelThree_更新'
this.caseModelFourArray.caseModelFourArray[0] = new CaseModelFour('caseModelFourArray_更新')
this.caseModelFive.caseModelFiveMap[0] = 'caseModelFiveMap_更新'
this.caseModelSix.caseModelSixSet = new Set(['4', '5', '6', '7']);
this.caseModelSeven.CaseModelSevenDate = new Date('2021-10-08')
})
}
.width('100%')
}
.height('100%')
}
}@ComponentV2装饰器:自定义组件
TIP
为了在自定义组件中使用V2版本状态变量装饰器的能力.开发者可以使用@ComponentV2装饰器装饰自定义组件
javascript
@ComponentV2 // 装饰器
struct Index { // struct声明的数据结构
build() { // build定义的UI
}
}@Local装饰器:组件内部状态
TIP
为了实现对@ComponentV2装饰的自定义组件中变量变化的观测,开发者可以使用@Local装饰器装饰变量
WARNING
使用场景:观察对象整体变化、装饰Date类型变量、Map类型变量、Set类型变量、联合类型
javascript
/** 观测对象整体变化*/
@ObservedV2
class CaseModelOne {
@Trace str1: string = 'CaseModelOne_str1'
}
/** Map类型*/
@ObservedV2
class CaseModelTwo {
@Trace caseModelFiveMap: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]])
}
/** Set类型*/
@ObservedV2
class CaseModelThree {
@Trace caseModelSixSet: Set<string> = new Set(['0', '1', '2', '3'])
}
/** Date类型*/
@ObservedV2
class CaseModelFour {
@Trace CaseModelSevenDate: Date = new Date('2021-08-08')
}
@Entry
@ComponentV2
struct LocalCase {
@Local caseModelOne: CaseModelOne = new CaseModelOne()
@Local caseModelTwo: CaseModelTwo = new CaseModelTwo()
@Local caseModelThree: CaseModelThree = new CaseModelThree()
@Local caseModelFour: CaseModelFour = new CaseModelFour()
build() {
Row() {
Column() {
Text(this.caseModelOne.str1)
.fontWeight(FontWeight.Bold)
Text(`${this.caseModelTwo.caseModelFiveMap.values()}`)
.fontWeight(FontWeight.Bold)
Text(`${this.caseModelThree.caseModelSixSet}`)
.fontWeight(FontWeight.Bold)
Text(`${this.caseModelFour.CaseModelSevenDate}`)
.fontWeight(FontWeight.Bold)
Button('change all value')
.onClick(() => {
this.caseModelOne.str1 = 'caseModelOne'
this.caseModelTwo.caseModelFiveMap.set(0, '2222')
this.caseModelThree.caseModelSixSet = new Set(['0', '1', '2', '3'])
this.caseModelFour.CaseModelSevenDate = new Date('2021-22-11')
})
}
.width('100%')
}
.height('100%')
}
}@Param:组件外部输入
TIP
为了增强子组件接受外部参数输入的能力.开发者可以使用@Param装饰器
WARNING
使用场景:从父组件到子组件变量与同步、装饰Date类型变量、Map类型变量、Set类型变量、联合类型
javascript
/** 基础类型*/
@ObservedV2
class CaseModelOne {
@Trace str1: string = 'CaseModelSix_原始'
}
/** Map类型*/
@ObservedV2
class CaseModelTwo {
@Trace caseModelFiveMap: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]])
}
/** Set类型*/
@ObservedV2
class CaseModelThree {
@Trace caseModelSixSet: Set<string> = new Set(['0', '1', '2', '3'])
}
/** Date类型*/
@ObservedV2
class CaseModelFour {
@Trace CaseModelSevenDate: Date = new Date('2021-08-08')
}
@Entry
@ComponentV2
struct ParamCase {
@Local caseModelOne: CaseModelOne = new CaseModelOne()
@Local caseModelTwo: CaseModelTwo = new CaseModelTwo()
@Local caseModelThree: CaseModelThree = new CaseModelThree()
@Local caseModelFour: CaseModelFour = new CaseModelFour()
build() {
Column() {
ParamCaseSon({
caseModelOne: this.caseModelOne,
caseModelTwo: this.caseModelTwo,
caseModelThree: this.caseModelThree,
caseModelFour: this.caseModelFour,
})
Button("change")
.onClick(() => {
this.caseModelOne.str1 = 'caseModelOne_更新'
this.caseModelTwo.caseModelFiveMap.set(0,'更新')
this.caseModelThree.caseModelSixSet = new Set(['更新'])
this.caseModelFour.CaseModelSevenDate = new Date('2021-2-22')
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
/**
* 自组件,Require标识,必传参数
*/
@ComponentV2
struct ParamCaseSon {
@Require @Param caseModelOne: CaseModelOne
@Require @Param caseModelTwo: CaseModelTwo
@Require @Param caseModelThree: CaseModelThree
@Require @Param caseModelFour: CaseModelFour
build() {
Column() {
Text(this.caseModelOne.str1)
Text(this.caseModelTwo.caseModelFiveMap.get(0))
Text(`${this.caseModelThree.caseModelSixSet}`)
Text(`${this.caseModelFour.CaseModelSevenDate}`)
}
}
}@Once:初始化同步一次
TIP
为了实现仅从外部初始化一次、不接受后续同步变化的能力.开发者可以使用@Once装饰器搭配@Param装饰器使用
WARNING
使用场景:变量仅初始化同步一次、本地修改@Param变量
javascript
@Entry
@ComponentV2
struct OnceCase {
@Local name: string = "xiaoxiao"
build() {
Column() {
Text(`name: ${this.name}`)
Button("change name")
.onClick(() => {
this.name = "dada";
})
OnceCaseSon({ name: this.name })
}
}
}
@ComponentV2
struct OnceCaseSon {
@Param @Once name: string = ""
build() {
Column() {
Text(`onceParam: ${this.name}`)
Button("change name")
// @Once与@Param搭配使用时,可以在本地修改@Param变量的值。
.onClick(() => {
this.name = 'honhon'
})
}
}
}@Event装饰器:规范组件输出
TIP
为了实现子组件向父组件要求更新@Param装饰变量的能力.开发者可以使用@Event装饰器。使用@Event装饰回调方法是一种规范.表明子组件需要传入更新数据源的回调
WARNING
使用场景:更改父组件中变量
javascript
@Entry
@ComponentV2
struct EventCase {
@Local name: string = "xiaoxiao"
build() {
Column() {
Text(`name: ${this.name}`)
EventCaseSon({ changeEventCaseName: () => this.name = 'EventCaseSon' })
}
}
}
@ComponentV2
struct EventCaseSon {
@Event changeEventCaseName: () => void = () => {}
build() {
Column() {
Button("changeEventCaseName")// @Once与@Param搭配使用时,可以在本地修改@Param变量的值。
.onClick(() => this.changeEventCaseName())
}
}
}@Provider装饰器和@Consumer装饰器:跨组件层级双向同步
TIP
为了实现子组件向父组件要求更新@Param装饰变量的能力.开发者可以使用@Event装饰器。使用@Event装饰回调方法是一种规范.表明子组件需要传入更新数据源的回调
javascript
/**
* @Provider和@Consumer可以装饰回调事件,用于组件之间完成行为抽象
*
* @Provider和@Consumer装饰复杂类型,配合@Trace一起使用
*
* !!! @Provider重名时,@Consumer向上查找其最近的@Provider
*
* 在子组件受用父组件用@Consumer获取的值会刷新或者初始化@Param装饰的值
*
*/
@Entry
@ComponentV2
struct ProviderAndProviderCase {
@Provider('name') name: string = '史迪黄'
build() {
Column() {
ProviderAndProviderSon()
}
}
}
@ComponentV2
struct ProviderAndProviderSon {
@Consumer('name') name: string = ''
// 如果未使用别名绑定将使用默认值进行显示
@Consumer('') age: number = 0
build() {
Column() {
Text(`form father value${this.name}`)
.onClick(() => {
this.name = '小小黄'
})
Text(`my age is${this.age}`)
// <---------------------->
ProviderAndProviderParam({ name: this.name })
}
.size({ width: '100%', height: '100%' })
}
}
@ComponentV2
struct ProviderAndProviderParam {
@Param @Require name: string
build() {
Column() {
Text(`form ProviderAndProviderSon value${this.name}`)
}
.size({ width: '100%', height: '100%s' })
}
}@Monitor装饰器:状态变量修改监听
TIP
为了增强状态管理框架对状态变量变化的监听能力.开发者可以使用@Monitor装饰器对状态变量进行监听
WARNING
监听深层属性变化
javascript
/**
* @Monitor监听的变量需要被@Local、@Param、@Provider、@Consumer、@Computed装饰
*
* @Monitor监听的状态变量为类对象时,仅能监听对象整体的变化。监听类属性的变化需要类属性被@Trace装饰
*
* @Monitor能够监听数组长度变化=========>@Monitor("Array.length")
*
* @Monitor能够监听对象属性变化=========>@Monitor("类明.属性名")
*/
import { promptAction } from '@kit.ArkUI'
@ObservedV2
class CaseModelOne {
@Trace name: string = 'CaseModelOne_原始'
@Monitor("name")
nameChange() {
promptAction.showToast({ message: `在类中name监听到变化类 变化值 ====>: ${this.name}` })
}
}
@Entry
@ComponentV2
struct MonitorCase {
@Local name: string = "史迪黄"
@Local age: number = 24
@Local caseModelOneName: CaseModelOne = new CaseModelOne()
@Monitor("name", "age", "caseModelOneName.name")
onStrChange(monitor: IMonitor) {
monitor.dirty.forEach((path: string) => {
console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`)
})
}
build() {
Column() {
Text(this.caseModelOneName.name)
Button("change caseModelOneName")
.onClick(() => this.caseModelOneName.name = "CaseModelOne_更新")
Button("change this.name")
.onClick(() => this.name = "史迪蓝")
Button("change this.age")
.onClick(() => this.age = 23)
}
}
}@Computed装饰器:计算属性
TIP
在被计算的值变化的时候,只会计算一次。主要应用于解决UI多次重用该属性从而重复计算导致的性能问题
WARNING
使用场景:当被计算的属性变化时,@Computed装饰的getter访问器只会被求解一次、@Computed装饰的属性可以被@Monitor监听变化、@Computed装饰的属性可以初始化@Param
javascript
/**
* 当被计算的属性变化时,@Computed装饰的getter访问器只会被求解一次
*
* @Computed装饰的属性可以被@Monitor监听变化
*
* @Computed装饰的属性可以初始化@Param
*/
@Entry
@ComponentV2
struct ComputedCase {
@Local count: number = 100;
@Computed
get double() {
return String(this.count * 2)
}
build() {
Scroll() {
Column({ space: 3 }) {
Text(this.double)
Text(this.double)
}
}
}
}@Type装饰器:标记类属性的类型
TIP
为了实现序列化类时不丢失属性的复杂类型.开发者可以使用@Type装饰器装饰类属性
WARNING
使用场景:持久化数据。
javascript
AppStorageV2: 应用全局UI状态存储
TIP
为了增强状态管理框架对应用全局UI状态变量存储的能力,开发者可以使用AppStorageV2存储应用全局UI状态变量数据
WARNING
使用场景:在两个页面之间存储数据
javascript
import { AppStorageV2, promptAction, router } from '@kit.ArkUI'
import { modelAll } from '../modelAll/modelAll'
/**
* connect:创建或获取储存的数据
*
* remove:删除指定key的储存数据
*
* keys:返回所有AppStorageV2中的key
*/
// 子页面
@Entry
@ComponentV2
export struct AppStorageV2Son {
@Local prop: modelAll = AppStorageV2.connect(modelAll, () => new modelAll())!
build() {
Column({ space: 10 }) {
Text(this.prop.p1.toString())
Button('改变modelAll下的p1数据').onClick((event: ClickEvent) => {
this.prop.p1 = 10000000
})
Button('删除指定数据').onClick((event: ClickEvent) => {
this.prop.p1 = 0
AppStorageV2.remove(modelAll)
})
Button('返回所有AppStorageV2,key').onClick((event: ClickEvent) => {
promptAction.showToast({ message: `${AppStorageV2.keys()}` })
})
Button('跳转').onClick((event: ClickEvent) => {
router.pushUrl({ url: 'otherDir/AppStorageV2SonPage' })
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
import { AppStorageV2, router } from '@kit.ArkUI'
import { modelAll } from '../modelAll/modelAll'
// 夫页面
@Entry
@ComponentV2
export struct AppStorageV2SonPage {
@Local prop: modelAll = AppStorageV2.connect(modelAll, () => new modelAll())!
build() {
Column({ space: 10 }) {
Text(this.prop.p1.toString())
Button('返回').onClick((event: ClickEvent) => {
router.pushUrl({ url: 'otherDir/AppStorageV2' })
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
@ObservedV2
export class modelAll {
@Trace p1: number = 0
@Trace p2: number = 10
}PersistenceV2: 持久化储存UI状态
TIP
为了增强状态管理框架对持久化存储UI的能力,开发者可以使用PersistenceV2存储持久化的数据
WARNING
使用场景:在两个页面之间存储数据
javascript
import { PersistenceV2 } from '@kit.ArkUI';
import { modelAll } from '../modelAll/modelAll';
// 接受序列化失败的回调
PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`)
});
/**
* 为了增强状态管理框架对持久化存储UI的能力,开发者可以使用PersistenceV2存储持久化的数据。
*
* PersistenceV2提供状态变量持久化能力,开发者可以通过connect绑定同一个key,在状态变量变换和应用冷启动时,实现持久化能力。
*/
@Entry
@ComponentV2
struct PersistenceV2Case {
@Local prop: modelAll = PersistenceV2.connect(modelAll, () => new modelAll())!
build() {
Column() {
Button('点击:connect')
.onClick(() => this.prop = PersistenceV2.connect(modelAll, 'Sample', () => new modelAll())!)
Button('点击:remove')
.onClick(() => PersistenceV2.remove(modelAll))
Button('点击:save')
.onClick(() => PersistenceV2.save(modelAll))
Text(`Page1 add 1 to prop.p1: ${this.prop.p1}`)
.fontSize(30)
.onClick(() => this.prop.p1++)
Text(`Page1 add 1 to prop.p2: ${this.prop.p1}`)
.fontSize(30)
.onClick(() => this.prop.p1++)
// 获取当前PersistenceV2里面的所有key
Text(`all keys in PersistenceV2: ${PersistenceV2.keys()}`)
.fontSize(30)
}
}
}
@ObservedV2
export class modelAll {
@Trace p1: number = 0
@Trace p2: number = 10
}!!语法:双向绑定
TIP
在状态管理V1中使用$$用于内置组件双向绑定
WARNING
使用场景:自定义组件间双向绑定,内置组件参数双向绑定
javascript
/**
* !!语法糖作为统一处理双向绑定语法。
*
* !!双向绑定语法不支持多层父子组件传递。
*/
@Entry
@ComponentV2
struct TwoWayCase {
@Local value: number = 0;
build() {
Column() {
Text(`${this.value}`)
Button(`change value`).onClick(() => {
this.value++;
})
TwoWayCaseSon({ value: this.value!! })
// 双向绑定语法糖可视为:
TwoWayCaseSon({
value: this.value, $value: (val: number) => {
this.value = val
}
})
}
}
}
@ComponentV2
struct TwoWayCaseSon {
@Param value: number = 0;
@Event $value: (val: number) => void = () => {
}
build() {
Column() {
Text(`${this.value}`)
Button(`change value `).onClick(() => this.$value(10))
}
}
}自定义组件冻结功能
TIP
当@ComponentV2装饰的自定义组件处于非激活状态时,状态变量将不响应更新,即@Monitor不会调用,状态变量关联的节点不会刷新
WARNING
使用场景:页面路由,TabContent,Navigation
javascript
/**
* 支持的场景有:页面路由,TabContent,Navigation。
*/
// 自定义组件冻结功能
@Entry
@ComponentV2
struct FreezeCase {
@Local message: number = 0;
@Local data: number[] = [0, 1];
build() {
Row() {
Column() {
Button('change message').onClick(() => {
this.message++;
})
Tabs() {
ForEach(this.data, (item: number) => {
TabContent() {
FreezeCaseSon({ message: this.message, index: item })
}.tabBar(`tab${item}`)
}, (item: number) => item.toString())
}
}
.width('100%')
}
.height('100%')
}
}
@ComponentV2({ freezeWhenInactive: true })
struct FreezeCaseSon {
@Param message: number = 0;
@Param index: number = 0;
@Monitor('message') onMessageUpdated(mon: IMonitor) {
console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`);
}
build() {
Text("message" + `${this.message}, index: ${this.index}`)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
}Repeat:子组件复用
TIP
Repeat基于数据源进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在Repeat父容器组件中的子组件
WARNING
使用场景:non-virtualScroll数据展示&操作,virtualScroll数据展示&操作,Repeat嵌套,父容器组件应用场景:与List组合使用,与Grid组合使用,与Swiper组合使用
javascript
/**
* non-virtualScroll模式和virtualScroll模式。
*
* non-virtualScroll模式:适合短数据列表/组件全部加载的场景。
*
* virtualScroll模式(开启virtualScroll开关):Repeat根据容器组件的有效加载范围(可视区域+预加载区域) 加载子组件。
*
* non-virtualScroll 仅作为数据改变-Repeat会复用之前的组件
*
*/
@Entry
@ComponentV2
struct RepeatCase {
@Local simpleList: Array<string> = ['one', 'two', 'three'];
build() {
Row() {
Column() {
Text('点击修改第3个数组项的值')
.fontSize(24)
.fontColor(Color.Red)
.onClick(() => {
this.simpleList[2] = 'new three';
})
Repeat<string>(this.simpleList)
.each((obj: RepeatItem<string>)=>{
RepeatCaseSon({ item: obj.item })
.margin({top: 20})
})
.key((item: string) => item)
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
.height('100%')
.backgroundColor(0xF1F3F5)
}
}
@ComponentV2
struct RepeatCaseSon {
@Param @Require item: string;
build() {
Text(this.item)
.fontSize(30)
}
}