143人参与 • 2024-08-06 • 鸿蒙系统
@prop装饰的变量可以和父组件建立单向的同步关系。@prop装饰的变量是可变的,但是变化不会同步回其父组件。
说明:
从api version 9开始,该装饰器支持在arkts卡片中使用。
@prop装饰的变量和父组件建立单向的同步关系:
● @prop变量允许在本地修改,但修改后的变化不会同步回父组件。
● 当数据源更改时,@prop装饰的变量都会更新,并且会覆盖本地所有更改。因此,数值的同步是父组件到子组件(所属组件),子组件数值的变化不会同步到父组件。
@prop变量装饰器 | 说明 |
---|---|
装饰器参数 | 无 |
同步类型 | 单向同步:对父组件状态变量值的修改,将同步给子组件@prop装饰的变量,子组件@prop变量的修改不会同步到父组件的状态变量上。嵌套类型的场景请参考观察变化。 |
允许装饰的变量类型 | object、class、string、number、boolean、enum类型,以及这些类型的数组。不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。支持date类型。支持类型的场景请参考观察变化。必须指定类型。说明 :不支持length、resourcestr、resourcecolor类型,length,resourcestr、resourcecolor为简单类型和复杂类型的联合类型。在父组件中,传递给@prop装饰的值不能为undefined或者null,反例如下所示。compa ({ aprop: undefined })compa ({ aprop: null })@prop和数据源类型需要相同,有以下三种情况:- @prop装饰的变量和@state以及其他装饰器同步时双方的类型必须相同,示例请参考父组件@state到子组件@prop简单数据类型同步。- @prop装饰的变量和@state以及其他装饰器装饰的数组的项同步时 ,@prop的类型需要和@state装饰的数组的数组项相同,比如@prop : t和@state : array<t>,示例请参考父组件@state数组中的项到子组件@prop简单数据类型同步;- 当父组件状态变量为object或者class时,@prop装饰的变量和父组件状态变量的属性类型相同,示例请参考从父组件中的@state类对象属性到@prop简单类型的同步。 |
嵌套传递层数 | 在组件复用场景,建议@prop深度嵌套数据不要超过5层,嵌套太多会导致深拷贝占用的空间过大以及garbagecollection(垃圾回收),引起性能问题,此时更建议使用@objectlink。如果子组件的数据不想同步回父组件,建议采用@reusable中的abouttoreuse,实现父组件向子组件传递数据,具体用例请参考组件复用场景。 |
被装饰变量的初始值 | 允许本地初始化。 |
传递/访问 | 说明 |
---|---|
从父组件初始化 | 如果本地有初始化,则是可选的。没有的话,则必选,支持父组件中的常规变量(常规变量对@prop赋值,只是数值的初始化,常规变量的变化不会触发ui刷新。只有状态变量才能触发ui刷新)、@state、@link、@prop、@provide、@consume、@objectlink、@storagelink、@storageprop、@localstoragelink和@localstorageprop去初始化子组件中的@prop变量。 |
用于初始化子组件 | @prop支持去初始化子组件中的常规变量、@state、@link、@prop、@provide。 |
是否支持组件外访问 | @prop装饰的变量是私有的,只能在组件内访问。 |
图1 初始化规则图示
@prop装饰的数据可以观察到以下变化。
● 当装饰的类型是允许的类型,即object、class、string、number、boolean、enum类型都可以观察到赋值的变化。
// 简单类型
@prop count: number;
// 赋值的变化可以被观察到
this.count = 1;
// 复杂类型
@prop count: model;
// 可以观察到赋值的变化
this.title = new model('hi');
当装饰的类型是object或者class复杂类型时,可以观察到第一层的属性的变化,属性即object.keys(observedobject)返回的所有属性;
class classa {
public value: string;
constructor(value: string) {
this.value = value;
}
}
class model {
public value: string;
public a: classa;
constructor(value: string, a: classa) {
this.value = value;
this.a = a;
}
}
@prop title: model;
// 可以观察到第一层的变化
this.title.value = 'hi'
// 观察不到第二层的变化
this.title.a.value = 'arkui'
对于嵌套场景,如果class是被@observed装饰的,可以观察到class属性的变化,示例请参考@prop嵌套场景。
当装饰的类型是数组的时候,可以观察到数组本身的赋值、添加、删除和更新。
// @state装饰的对象为数组时
@prop title: string[]
// 数组自身的赋值可以观察到
this.title = ['1']
// 数组项的赋值可以观察到
this.title[0] = '2'
// 删除数组项可以观察到
this.title.pop()
// 新增数组项可以观察到
this.title.push('3')
对于@state和@prop的同步场景:
● 使用父组件中@state变量的值初始化子组件中的@prop变量。当@state变量变化时,该变量值也会同步更新至@prop变量。
● @prop装饰的变量的修改不会影响其数据源@state装饰变量的值。
● 除了@state,数据源也可以用@link或@prop装饰,对@prop的同步机制是相同的。
● 数据源和@prop变量的类型需要相同,@prop允许简单类型和class类型。
● 当装饰的对象是date时,可以观察到date整体的赋值,同时可通过调用date的接口setfullyear, setmonth, setdate, sethours, setminutes, setseconds, setmilliseconds, settime, setutcfullyear, setutcmonth, setutcdate, setutchours, setutcminutes, setutcseconds, setutcmilliseconds 更新date的属性。
@component
struct datecomponent {
@prop selecteddate: date = new date('');
build() {
column() {
button('child update the new date')
.margin(10)
.onclick(() => {
this.selecteddate = new date('2023-09-09')
})
button(`child increase the year by 1`).onclick(() => {
this.selecteddate.setfullyear(this.selecteddate.getfullyear() + 1)
})
datepicker({
start: new date('1970-1-1'),
end: new date('2100-1-1'),
selected: this.selecteddate
})
}
}
}
@entry
@component
struct parentcomponent {
@state parentselecteddate: date = new date('2021-08-08');
build() {
column() {
button('parent update the new date')
.margin(10)
.onclick(() => {
this.parentselecteddate = new date('2023-07-07')
})
button('parent increase the day by 1')
.margin(10)
.onclick(() => {
this.parentselecteddate.setdate(this.parentselecteddate.getdate() + 1)
})
datepicker({
start: new date('1970-1-1'),
end: new date('2100-1-1'),
selected: this.parentselecteddate
})
datecomponent({selecteddate:this.parentselecteddate})
}
}
}
要理解@prop变量值初始化和更新机制,有必要了解父组件和拥有@prop变量的子组件初始渲染和更新流程。
1. 初始渲染:
a. 执行父组件的build()函数将创建子组件的新实例,将数据源传递给子组件;
b. 初始化子组件@prop装饰的变量。
2. 更新:
a. 子组件@prop更新时,更新仅停留在当前子组件,不会同步回父组件;
b. 当父组件的数据源更新时,子组件的@prop装饰的变量将被来自父组件的数据源重置,所有@prop装饰的本地的修改将被父组件的更新覆盖。
以下示例是@state到子组件@prop简单数据同步,父组件parentcomponent的状态变量countdownstartvalue初始化子组件countdowncomponent中@prop装饰的count,点击“try again”,count的修改仅保留在countdowncomponent 不会同步给父组件parentcomponent。
parentcomponent的状态变量countdownstartvalue的变化将重置countdowncomponent的count。
@component
struct countdowncomponent {
@prop count: number = 0;
costofoneattempt: number = 1;
build() {
column() {
if (this.count > 0) {
text(`you have ${this.count} nuggets left`)
} else {
text('game over!')
}
// @prop装饰的变量不会同步给父组件
button(`try again`).onclick(() => {
this.count -= this.costofoneattempt;
})
}
}
}
@entry
@component
struct parentcomponent {
@state countdownstartvalue: number = 10;
build() {
column() {
text(`grant ${this.countdownstartvalue} nuggets to play.`)
// 父组件的数据源的修改会同步给子组件
button(`+1 - nuggets in new game`).onclick(() => {
this.countdownstartvalue += 1;
})
// 父组件的修改会同步给子组件
button(`-1 - nuggets in new game`).onclick(() => {
this.countdownstartvalue -= 1;
})
countdowncomponent({ count: this.countdownstartvalue, costofoneattempt: 2 })
}
}
}
在上面的示例中:
1. countdowncomponent子组件首次创建时其@prop装饰的count变量将从父组件@state装饰的countdownstartvalue变量初始化;
2. 按“+1”或“-1”按钮时,父组件的@state装饰的countdownstartvalue值会变化,这将触发父组件重新渲染,在父组件重新渲染过程中会刷新使用countdownstartvalue状态变量的ui组件并单向同步更新countdowncomponent子组件中的count值;
3. 更新count状态变量值也会触发countdowncomponent的重新渲染,在重新渲染过程中,评估使用count状态变量的if语句条件(this.count > 0),并执行true分支中的使用count状态变量的ui组件相关描述来更新text组件的ui显示;
4. 当按下子组件countdowncomponent的“try again”按钮时,其@prop变量count将被更改,但是count值的更改不会影响父组件的countdownstartvalue值;
5. 父组件的countdownstartvalue值会变化时,父组件的修改将覆盖掉子组件countdowncomponent中count本地的修改。
父组件中@state如果装饰的数组,其数组项也可以初始化@prop。以下示例中父组件index中@state装饰的数组arr,将其数组项初始化子组件child中@prop装饰的value。
@component
struct child {
@prop value: number = 0;
build() {
text(`${this.value}`)
.fontsize(50)
.onclick(()=>{this.value++})
}
}
@entry
@component
struct index {
@state arr: number[] = [1,2,3];
build() {
row() {
column() {
child({value: this.arr[0]})
child({value: this.arr[1]})
child({value: this.arr[2]})
divider().height(5)
foreach(this.arr,
(item: void) => {
child({value: item})
},
(item: string) => item.tostring()
)
text('replace entire arr')
.fontsize(50)
.onclick(()=>{
// 两个数组都包含项“3”。
this.arr = this.arr[0] == 1 ? [3,4,5] : [1,2,3];
})
}
}
}
}
初始渲染创建6个子组件实例,每个@prop装饰的变量初始化都在本地拷贝了一份数组项。子组件onclick事件处理程序会更改局部变量值。
假设我们点击了多次,所有变量的本地取值都是“7”。
7
7
7
----
7
7
7
单击replace entire arr后,屏幕将显示以下信息。
3
4
5
----
7
4
5
● 在子组件child中做的所有的修改都不会同步回父组件index组件,所以即使6个组件显示都为7,但在父组件index中,this.arr保存的值依旧是[1,2,3]。
● 点击replace entire arr,this.arr[0] == 1成立,将this.arr赋值为[3, 4, 5];
● 因为this.arr[0]已更改,child({value: this.arr[0]})组件将this.arr[0]更新同步到实例@prop装饰的变量。child({value: this.arr[1]})和child({value: this.arr[2]})的情况也类似。
● this.arr的更改触发foreach更新,this.arr更新的前后都有数值为3的数组项:[3, 4, 5] 和[1, 2, 3]。根据diff机制,数组项“3”将被保留,删除“1”和“2”的数组项,添加为“4”和“5”的数组项。这就意味着,数组项“3”的组件不会重新生成,而是将其移动到第一位。所以“3”对应的组件不会更新,此时“3”对应的组件数值为“7”,foreach最终的渲染结果是“7”,“4”,“5”。
如果图书馆有一本图书和两位用户,每位用户都可以将图书标记为已读,此标记行为不会影响其它读者用户。从代码角度讲,对@prop图书对象的本地更改不会同步给图书馆组件中的@state图书对象。
在此示例中,图书类可以使用@observed装饰器,但不是必须的,只有在嵌套结构时需要此装饰器。这一点我们会在从父组件中的@state数组项到@prop class类型的同步说明。
class book {
public title: string;
public pages: number;
public readit: boolean = false;
constructor(title: string, pages: number) {
this.title = title;
this.pages = pages;
}
}
@component
struct readercomp {
@prop book: book = new book("", 0);
build() {
row() {
text(this.book.title)
text(`...has${this.book.pages} pages!`)
text(`...${this.book.readit ? "i have read" : 'i have not read it'}`)
.onclick(() => this.book.readit = true)
}
}
}
@entry
@component
struct library {
@state book: book = new book('100 secrets of c++', 765);
build() {
column() {
readercomp({ book: this.book })
readercomp({ book: this.book })
}
}
}
在下面的示例中,更改了@state 修饰的allbooks数组中book对象上的属性,但点击“mark read for everyone”无反应。这是因为该属性是第二层的嵌套属性,@state装饰器只能观察到第一层属性,不会观察到此属性更改,所以框架不会更新readercomp。
let nextid: number = 1;
// @observed
class book {
public id: number;
public title: string;
public pages: number;
public readit: boolean = false;
constructor(title: string, pages: number) {
this.id = nextid++;
this.title = title;
this.pages = pages;
}
}
@component
struct readercomp {
@prop book: book = new book("", 1);
build() {
row() {
text(this.book.title)
text(`...has${this.book.pages} pages!`)
text(`...${this.book.readit ? "i have read" : 'i have not read it'}`)
.onclick(() => this.book.readit = true)
}
}
}
@entry
@component
struct library {
@state allbooks: book[] = [new book("100 secrets of c++", 765), new book("effective c++", 651), new book("the c++ programming language", 1765)];
build() {
column() {
text('library`s all time favorite')
readercomp({ book: this.allbooks[2] })
divider()
text('books on loaan to a reader')
foreach(this.allbooks, (book: book) => {
readercomp({ book: book })
},
(book: book) => book.id.tostring())
button('add new')
.onclick(() => {
this.allbooks.push(new book("the c++ standard library", 512));
})
button('remove first book')
.onclick(() => {
this.allbooks.shift();
})
button("mark read for everyone")
.onclick(() => {
this.allbooks.foreach((book) => book.readit = true)
})
}
}
}
需要使用@observed装饰class book,book的属性将被观察。 需要注意的是,@prop在子组件装饰的状态变量和父组件的数据源是单向同步关系,即readercomp中的@prop book的修改不会同步给父组件library。而父组件只会在数值有更新的时候(和上一次状态的对比),才会触发ui的重新渲染。
@observed
class book {
public id: number;
public title: string;
public pages: number;
public readit: boolean = false;
constructor(title: string, pages: number) {
this.id = nextid++;
this.title = title;
this.pages = pages;
}
}
@observed装饰的类的实例会被不透明的代理对象包装,此代理可以检测到包装对象内的所有属性更改。如果发生这种情况,此时,代理通知@prop,@prop对象值被更新。
为了支持@component装饰的组件复用场景,@prop支持本地初始化,这样可以让@prop是否与父组件建立同步关系变得可选。当且仅当@prop有本地初始化时,从父组件向子组件传递@prop的数据源才是可选的。
下面的示例中,子组件包含两个@prop变量:
● @prop customcounter没有本地初始化,所以需要父组件提供数据源去初始化@prop,并当父组件的数据源变化时,@prop也将被更新;
● @prop customcounter2有本地初始化,在这种情况下,@prop依旧允许但非强制父组件同步数据源给@prop。
@component
struct mycomponent {
@prop customcounter: number = 0;
@prop customcounter2: number = 5;
build() {
column() {
row() {
text(`from main: ${this.customcounter}`).width(90).height(40).fontcolor('#ff0010')
}
row() {
button('click to change locally !').width(180).height(60).margin({ top: 10 })
.onclick(() => {
this.customcounter2++
})
}.height(100).width(180)
row() {
text(`custom local: ${this.customcounter2}`).width(90).height(40).fontcolor('#ff0010')
}
}
}
}
@entry
@component
struct mainprogram {
@state maincounter: number = 10;
build() {
column() {
row() {
column() {
button('click to change number').width(480).height(60).margin({ top: 10, bottom: 10 })
.onclick(() => {
this.maincounter++
})
}
}
row() {
column() {
// customcounter必须从父组件初始化,因为mycomponent的customcounter成员变量缺少本地初始化;此处,customcounter2可以不做初始化。
mycomponent({ customcounter: this.maincounter })
// customcounter2也可以从父组件初始化,父组件初始化的值会覆盖子组件customcounter2的本地初始化的值
mycomponent({ customcounter: this.maincounter, customcounter2: this.maincounter })
}
}
}
}
}
在嵌套场景下,每一层都要用@observed装饰,且每一层都要被@prop接收,这样才能观察到嵌套场景。
// 以下是嵌套类对象的数据结构。
@observed
class classa {
public title: string;
constructor(title: string) {
this.title = title;
}
}
@observed
class classb {
public name: string;
public a: classa;
constructor(name: string, a: classa) {
this.name = name;
this.a = a;
}
}
以下组件层次结构呈现的是@prop嵌套场景的数据结构。
@entry
@component
struct parent {
@state votes: classb = new classb('hello', new classa('world'))
build() {
column() {
button('change')
.onclick(() => {
this.votes.name = "aaaaa"
this.votes.a.title = "wwwww"
})
child({ vote: this.votes })
}
}
}
@component
struct child {
@prop vote: classb = new classb('', new classa(''));
build() {
column() {
text(this.vote.name).fontsize(36).fontcolor(color.red).margin(50)
.onclick(() => {
this.vote.name = 'bye'
})
text(this.vote.a.title).fontsize(36).fontcolor(color.blue)
.onclick(() => {
this.vote.a.title = "openharmony"
})
child1({vote1:this.vote.a})
}
}
}
@component
struct child1 {
@prop vote1: classa = new classa('');
build() {
column() {
text(this.vote1.title).fontsize(36).fontcolor(color.red).margin(50)
.onclick(() => {
this.vote1.title = 'bye bye'
})
}
}
}
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论