ComponentV2装饰器:自定义组件
ComponentV2装饰器用于装饰自定义组件,需要使用全新的状态变量装饰器,包括@Local、@Param、@Once、@Event、@Provider、@Consumer等。
最简单的自定义组件至少需要包含以下部分。
@ComponentV2 // 装饰器
struct Index { // struct声明的数据结构
build() { // build定义的UI
}
}
Local装饰器:组件内部状态
1、被@Local装饰的变量无法从外部初始化,因此必须在组件内部进行初始化;
2、@Local支持观测number、boolean、string、Object、class等基本类型以及Array、Set、Map、Date等内嵌类型;
3、@Local的观测能力仅限于被装饰的变量本身。当装饰简单类型时,能够观测到对变量的赋值;当装饰对象类型时,仅能观测到对对象整体的赋值;当装饰数组类型时,能观测到数组整体以及数组元素项的变化;当装饰Array、Set、Map、Date等内嵌类型时,可以观测到通过API调用带来的变化;
4、@Local支持null、undefined以及联合类型。
装饰Date类型变量
当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。
装饰Map类型变量
当装饰的对象是Map时,可以观察到对Map整体的赋值,同时可以通过调用Map的接口 set、clear、delete更新Map中的数据。
装饰Set类型变量
当装饰的对象是Set时,可以观察到对Set整体的赋值,同时可以通过调用Set的接口add、clear、delete更新Set中的数据。
联合类型
@Local支持null、undefined以及联合类型。在下面的示例中,count类型为number | undefined,点击改变count的类型,UI会随之刷新。
Param:组件外部输入
Param表示组件从外部传入的状态,使得父子组件之间的数据能够进行同步:
1、Param装饰的变量支持本地初始化,但不允许在组件内部直接修改;
2、被@Param装饰的变量能够在初始化自定义组件时从外部传入,当数据源也是状态变量时,数据源的修改会同步给@Param;
3、@Param可以接受任意类型的数据源,包括普通变量、状态变量、常量、函数返回值等;
4、@Param支持观测number、boolean、string、Object、class等基本类型以及Array、Set、Map、Date等内嵌类型;
5、对于复杂类型如类对象,@Param会接受数据源的引用。在组件内可以修改类对象中的属性,该修改会同步到数据源;
6、@Param的观测能力仅限于被装饰的变量本身。当装饰简单类型时,可以观测变量的整体改变。当装饰对象类型时,仅能观测对象整体的改变。当装饰数组类型时,可以观测数组整体和元素项的改变。当装饰Array、Set、Map、Date等内嵌类型时,可以观测到通过API调用带来的变化;
7、@Param支持null、undefined以及联合类型。
Once:初始化同步一次
Once装饰器在变量初始化时接受外部传入值进行初始化,后续数据源更改不会同步给子组件:
1、@Once必须搭配@Param使用,单独使用或搭配其他装饰器使用都是不允许的。
2、@Once不影响@Param的观测能力,仅针对数据源的变化做拦截。
3、@Once与@Param装饰变量的先后顺序不影响使用功能。
4、@Once与@Param搭配使用时,可以在本地修改@Param变量的值。
Event装饰器:规范组件输出
由于@Param装饰的变量在本地无法更改,使用@Event装饰器装饰回调方法并调用,可以实现更改数据源的变量,再通过@Local的同步机制,将修改同步回@Param,以此达到主动更新@Param装饰变量的效果。
1、@Event装饰的回调方法中参数以及返回值由开发者决定。
2、@Event装饰非回调类型的变量不会生效。当@Event没有初始化时,会自动生成一个空的函数作为默认回调。
3、当@Event未被外部初始化,但本地有默认值时,会使用本地默认的函数进行处理。
4、@Param标志着组件的输入,表明该变量受父组件影响,而@Event标志着组件的输出,可以通过该方法影响父组件。使用@Event装饰回调方法是一种规范,表明该回调作为自定义组件的输出。父组件需要判断是否提供对应方法用于子组件更改@Param变量的数据源。
更改父组件中变量
使用@Event可以更改父组件中变量,当该变量作为子组件@Param变量的数据源时,该变化会同步回子组件的@Param变量。
@ComponentV2
export struct paramTestComponent{
@Require @Param isShowName:boolean
@Event setShow:(isShow:boolean)=>void = ()=>{}
build() {
Column(){
if (this.isShowName){
Text("是否显示!")
.fontSize(20)
}
Button("显示")
.onClick(()=>{
// this.isShowName = true Cannot assign to 'isShowName' because it is a read-only property
this.setShow(!this.isShowName)
})
}.width(300)
.height(300)
.margin({left:20})
}
}
@Entry
@ComponentV2
struct Index {
@Local isShow:boolean = false
build() {
Column({space:20}){
paramTestComponent({isShowName:this.isShow,setShow:(isShow:boolean)=>{this.isShow = isShow}})
}.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
.padding({left:20})
}
}
Provider装饰器和@Consumer装饰器:跨组件层级双向同步
@Provider和@Consumer提供了跨组件层级数据双向同步的能力,
@Provider支持属性的类型可以为number、string、boolean、class、Array、Date、Map、Set等类型。支持装饰箭头函数,需要本地初始化。
@Consumer属性装饰器,可装饰的变量自定义组件中成员变量。属性的类型可以为number、string、boolean、class、Array、Date、Map、Set等类型。支持装饰箭头函数,必须本地初始化。
使用限制
- @Provider和@Consumer为自定义组件的属性装饰器,只能装饰自定义组件内的属性,不能装饰class的属性。
- @Provider和@Consumer为状态管理V2装饰器,只能在@ComponentV2中使用,不能在@Component中使用。
- @Provider和@Consumer只支持本地初始化,不支持外部传入初始化。
使用场景
@Provider和@Consumer双向同步
- 点击index中的Button,改变@Provider装饰的text,通知其对应的@Consumer,对应UI刷新。
- 点击Child中Button,改变@Consumer装饰的text,通知其对应的@Provider,对应UI刷新。
-
@ComponentV2 struct child{ //如果message没找到,将显示默认值“My own news!” @Consumer('message') text:string = "My own news!" build() { Column({space:20}){ Text('我是子组件:'+this.text) .fontSize(20) Button('子组件改变值').onClick(()=>{ this.text = "孩子说,这是一条消息" }) }.justifyContent(FlexAlign.Start) .alignItems(HorizontalAlign.Start) } } @Entry @ComponentV2 struct Index { @Provider("message") text:string = "this is a message!" build() { Column({space:20}){ Text('我是父组件:'+this.text) .fontSize(20) Button('父组件改变值').onClick(()=>{ this.text = "父亲说,这是一条消息" }) Divider() child() }.justifyContent(FlexAlign.Start) .alignItems(HorizontalAlign.Start) .padding({left:20}) } }
@Provider和@Consumer装饰回调事件.
通过点击按钮给回调函数,传去名字
@ComponentV2
struct setNameComponent{
@Consumer("getName") setName:(name:string)=>void = (name:string)=>{}
build() {
Button('我来给你提供名字!').onClick(()=>{
this.setName("拉斐尔")
})
}
}
@Entry
@ComponentV2
struct Index {
@Local name:string = ""
@Provider("getName") getMyName:(name:string)=>void = (name:string)=>{
this.name = name
}
build() {
Column({space:20}){
Text('我的名字是:'+this.name)
.fontSize(20)
Divider()
setNameComponent()
}.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
.padding({left:20})
}
}
@Provider和@Consumer装饰复杂类型,配合@Trace一起使用
- @Provider和@Consumer只能观察到数据本身的变化。如果需要观察其装饰的复杂数据类型的属性变化,必须配合@Trace一起使用。
- 装饰内置类型:Array、Map、Set、Date时,可以观察到某些API的变化,观察能力同@Trace。
@Provider重名时,@Consumer向上查找其最近的@Provider
@Provider可以在组件树上重名,@Consumer会向上查找其最近父节点的@Provider的数据。
组件一和二同样发出了‘NameRep’,组件三只接受组件二的消息.
@ComponentV2
struct parent{
@Provider('NameRep') myName:string = "我是组件二"
@Consumer('NameRep') getName:string = "我是组件二"
build() {
Column(){
Text('我是组件二接收:'+this.getName)
.fontSize(20)
Divider()
child1()
}
}
}
@ComponentV2
struct child1{
@Consumer('NameRep') getName:string = "我是组件三"
build() {
Column(){
Text('我是组件三接收:'+this.getName)
.fontSize(20)
Divider()
}
}
}
@Entry
@ComponentV2
struct Index {
@Provider('NameRep') myName:string = "我是组件一"
build() {
Column({space:20}){
Text(this.myName)
.fontSize(20)
Divider()
parent()
}.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
.padding({left:20})
}
}
Monitor装饰器:状态变量修改监听
为了增强状态管理框架对状态变量变化的监听能力,开发者可以使用@Monitor装饰器对状态变量进行监听。@Monitor提供了对V2状态变量的监听。
@Monitor装饰器支持在@ComponentV2装饰的自定义组件和类中使用。
单个@Monitor装饰器能够同时监听多个属性的变化,当这些属性在一次事件中共同变化时,只会触发一次@Monitor的回调方法。
@Monitor装饰器具有深度监听的能力,能够监听嵌套类、多维数组、对象数组中指定项的变化。对于嵌套类、对象数组中成员属性变化的监听要求该类被@ObservedV2装饰且该属性被@Trace装饰。
在继承类场景中,可以在父子组件中对同一个属性分别定义@Monitor进行监听,当属性变化时,父子组件中定义的@Monitor回调均会被调用。
在@ComponentV2装饰的自定义组件中使用@Monitor
Monitor监听local修饰的message,当点击翻译按钮,onMessageChange会被调用。当监听对象变为class类时,未被trace修饰。当点击‘刷新info’按钮时,onInfoChange事件被调用,点击‘刷新info.name’时onInfoNameChange无响应。
@Entry
@ComponentV2
struct Index {
@Local message:string = "this is a message!"
@Monitor('message')
onMessageChange(monitor:IMonitor){
console.log(''+monitor.value()?.before)
console.log(''+monitor.value()?.now)
}
@Local info:info = new info()
@Monitor('info')
onInfoChange(m:IMonitor){
console.log(''+m.value()?.before)
console.log(''+m.value()?.now)
}
@Monitor('info.name')
onInfoNameChange(m:IMonitor){
console.log(''+m.value()?.before)
console.log(''+m.value()?.now)
}
build() {
Column({space:20}){
Text(this.message)
.fontSize(20)
Button('翻译').onClick(()=>{
this.message = "这是一条消息"
})
Button('刷新info').onClick(()=>{
this.info = new info()
})
Button('刷新info.name').onClick(()=>{
this.info.name = "米开朗琪罗"
})
}.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
.padding({left:20})
}
」
在@ObservedV2装饰的类中使用@Monitor
使用@Monitor监听的属性发生变化时,会触发@Monitor的回调方法。
- @Monitor监听的对象属性需要被@Trace装饰,未被@Trace装饰的属性的变化无法被监听。@Monitor可以同时监听多个属性,这些属性之间用","隔开。
类中定义的@Monitor随着类的销毁失效。而由于类的实际销毁释放依赖于垃圾回收机制,因此会出现即使所在自定义组件已经销毁,类却还未及时销毁,导致类中定义的@Monitor仍在监听变化的情况。
Computed装饰器:计算属性
计算属性,当被计算的属性变化时,只会计算一次。这解决了UI多次重用该属性导致的重复计算和性能问题。
当被计算的属性变化时,@Computed装饰的getter访问器只会被求解一次
@Entry
@ComponentV2
struct Index {
@Local firstName:string = "拉斐尔"
@Local lastName:string = "桑西"
@Computed
get FullName(){
return this.firstName + '.' +this.lastName
}
build() {
Column({space:20}){
Text(this.FullName)
.fontSize(20)
Button('改第一个名').onClick(()=>{
this.firstName = '小拉斐尔'
})
Button('改第二个名').onClick(()=>{
this.lastName = '桑冬'
})
}.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
.padding({left:20})
}
}
ReusableV2装饰器:组件复用
@ReusableV2同样提供了aboutToRecycle和aboutToReuse的生命周期,在组件被回收时调用aboutToRecycle,在组件被复用时调用aboutToReuse,但与@Reusable不同的是,aboutToReuse没有入参。
在回收阶段,会递归地调用所有子组件的aboutToRecycle回调(即使子组件未被标记可复用);在复用阶段,会递归地调用所有子组件的aboutToReuse回调(即使子组件未被标记可复用)。
@ReusableV2装饰的自定义组件会在被回收期间保持冻结状态,即无法触发UI刷新、无法触发@Monitor回调,与freezeWhenInactive标记位不同的是,在解除冻结状态后,不会触发延后的刷新。
@ReusableV2装饰的自定义组件会在复用时自动重置组件内状态变量的值、重新计算组件内@Computed以及与之相关的@Monitor。