当前位置: 首页 > news >正文

加强主流网站集群传播能力建设营销型网站建设网站建设资讯

加强主流网站集群传播能力建设,营销型网站建设网站建设资讯,做网站需不需要购买服务器,社交app开发应用通过状态去渲染更新UI是程序设计中相对复杂#xff0c;但又十分重要的#xff0c;往往决定了应用程序的性能。程序的状态数据通常包含了数组、对象#xff0c;或者是嵌套对象组合而成。在这些情况下#xff0c;ArkUI采取MVVM Model View ViewModel模式#xff0c;其…应用通过状态去渲染更新UI是程序设计中相对复杂但又十分重要的往往决定了应用程序的性能。程序的状态数据通常包含了数组、对象或者是嵌套对象组合而成。在这些情况下ArkUI采取MVVM Model View ViewModel模式其中状态管理模块起到的就是ViewModel的作用将数据与视图绑定在一起更新数据的时候直接更新视图。 Model层存储数据和相关逻辑的模型。它表示组件或其他相关业务逻辑之间传输的数据。Model是对原始数据的进一步处理。 View层在ArkUI中通常是Components修饰组件渲染的UI。 ViewModel层在ArkUI中ViewModel是存储在自定义组件的状态变量、LocalStorage和AppStorage中的数据。 自定义组件通过执行其build()方法或者Builder装饰的方法来渲染UI即ViewModel可以渲染View。View可以通过相应event handler来改变ViewModel即事件驱动ViewModel的改变另外ViewModel提供了Watch回调方法用于监听状态数据的改变。在ViewModel被改变时需要同步回Model层这样才能保证ViewModel和Model的一致性即应用自身数据的一致性。ViewModel结构设计应始终为了适配自定义组件的构建和更新这也是将Model和ViewModel分开的原因。 目前很多关于UI构造和更新的问题都是由于ViewModel的设计并没有很好的支持自定义组件的渲染或者试图去让自定义组件强行适配Model层而中间没有用ViewModel来进行分离。例如一个应用程序直接将SQL数据库中的数据读入内存这种数据模型不能很好的直接适配自定义组件的渲染所以在应用程序开发中需要适配ViewModel层。 根据上面涉及SQL数据库的示例应用程序应设计为 Model针对数据库高效操作的数据模型。 ViewModel针对ArkUI状态管理功能进行高效的UI更新的视图模型。 部署 converters/adapters converters/adapters作用于Model和ViewModel的相互转换。 converters/adapters可以转换最初从数据库读取的Model来创建并初始化ViewModel。在应用的使用场景中UI会通过event handler改变ViewModel此时converters/adapters需要将ViewModel的更新数据同步回Model。 虽然与强制将UI拟合到SQL数据库模式MV模式相比MVVM的设计比较复杂但应用程序开发人员可以通过ViewModel层的隔离来简化UI的设计和实现以此来收获更好的UI性能。 ViewModel的数据源 ViewModel通常包含多个顶层数据源。State和Provide装饰的变量以及LocalStorage和AppStorage都是顶层数据源其余装饰器都是与数据源做同步的数据。装饰器的选择取决于状态需要在自定义组件之间的共享范围。共享范围从小到大的排序是 State组件级别的共享通过命名参数机制传递例如CompA: ({ aProp: this.aProp })表示传递层级共享范围是父子之间的传递。 Provide组件级别的共享可以通过key和Consume绑定因此不用参数传递实现多层级的数据共享共享范围大于State。 LocalStorage页面级别的共享可以通过Entry在当前组件树上共享LocalStorage实例。 AppStorage应用全局的UI状态存储和应用进程绑定在整个应用内的状态数据的共享。 State装饰的变量与一个或多个子组件共享状态数据 State可以初始化多种状态变量Prop、Link和ObjectLink可以和其建立单向或双向同步。 使用Parent根节点中State装饰的testNum作为ViewModel数据项。将testNum传递给其子组件LinkChild和Sibling。 // xxx.ets Entry Component struct Parent {State Watch(testNumChange1) testNum: number 1;testNumChange1(propName: string): void {console.log(Parent: testNumChange value ${this.testNum})}build() {Column() {LinkChild({ testNum: $testNum })Sibling({ testNum: $testNum })}} }LinkChild和Sibling中用Link和父组件的数据源建立双向同步。其中LinkChild中创建了LinkLinkChild和PropLinkChild。 Component struct Sibling {Link Watch(testNumChange) testNum: number;testNumChange(propName: string): void {console.log(Sibling: testNumChange value ${this.testNum});}build() {Text(Sibling: ${this.testNum})} }Component struct LinkChild {Link Watch(testNumChange) testNum: number;testNumChange(propName: string): void {console.log(LinkChild: testNumChange value ${this.testNum});}build() {Column() {Button(incr testNum).onClick(() {console.log(LinkChild: before value change value ${this.testNum});this.testNum this.testNum 1console.log(LinkChild: after value change value ${this.testNum});})Text(LinkChild: ${this.testNum})LinkLinkChild({ testNumGrand: $testNum })PropLinkChild({ testNumGrand: this.testNum })}.height(200).width(200)} }LinkLinkChild和PropLinkChild声明如下PropLinkChild中的Prop和其父组件建立单向同步关系。 Component struct LinkLinkChild {Link Watch(testNumChange) testNumGrand: number;testNumChange(propName: string): void {console.log(LinkLinkChild: testNumGrand value ${this.testNumGrand});}build() {Text(LinkLinkChild: ${this.testNumGrand})} }Component struct PropLinkChild {Prop Watch(testNumChange) testNumGrand: number 0;testNumChange(propName: string): void {console.log(PropLinkChild: testNumGrand value ${this.testNumGrand});}build() {Text(PropLinkChild: ${this.testNumGrand}).height(70).backgroundColor(Color.Red).onClick(() {this.testNumGrand 1;})} }当LinkChild中的Link testNum更改时。 更改首先同步到其父组件Parent然后更改从Parent同步到Sibling。 LinkChild中的Link testNum更改也同步给子组件LinkLinkChild和PropLinkChild。 State装饰器与Provide、LocalStorage、AppStorage的区别 State如果想要将更改传递给孙子节点需要先将更改传递给子组件再从子节点传递给孙子节点。共享只能通过构造函数的参数传递即命名参数机制CompA: ({ aProp: this.aProp })。 完整的代码示例如下 Component struct LinkLinkChild {Link Watch(testNumChange) testNumGrand: number;testNumChange(propName: string): void {console.log(LinkLinkChild: testNumGrand value ${this.testNumGrand});}build() {Text(LinkLinkChild: ${this.testNumGrand})} }Component struct PropLinkChild {Prop Watch(testNumChange) testNumGrand: number 0;testNumChange(propName: string): void {console.log(PropLinkChild: testNumGrand value ${this.testNumGrand});}build() {Text(PropLinkChild: ${this.testNumGrand}).height(70).backgroundColor(Color.Red).onClick(() {this.testNumGrand 1;})} }Component struct Sibling {Link Watch(testNumChange) testNum: number;testNumChange(propName: string): void {console.log(Sibling: testNumChange value ${this.testNum});}build() {Text(Sibling: ${this.testNum})} }Component struct LinkChild {Link Watch(testNumChange) testNum: number;testNumChange(propName: string): void {console.log(LinkChild: testNumChange value ${this.testNum});}build() {Column() {Button(incr testNum).onClick(() {console.log(LinkChild: before value change value ${this.testNum});this.testNum this.testNum 1console.log(LinkChild: after value change value ${this.testNum});})Text(LinkChild: ${this.testNum})LinkLinkChild({ testNumGrand: $testNum })PropLinkChild({ testNumGrand: this.testNum })}.height(200).width(200)} }Entry Component struct Parent {State Watch(testNumChange1) testNum: number 1;testNumChange1(propName: string): void {console.log(Parent: testNumChange value ${this.testNum})}build() {Column() {LinkChild({ testNum: $testNum })Sibling({ testNum: $testNum })}} }Provide装饰的变量与任何后代组件共享状态数据 Provide装饰的变量可以与任何后代组件共享状态数据其后代组件使用Consume创建双向同步详情见Provide和Consume。 因此Provide-Consume模式比使用State-Link-Link从父组件将更改传递到孙子组件更方便。Provide-Consume适合在单个页面UI组件树中共享状态数据。 使用Provide-Consume模式时Consume和其祖先组件中的Provide通过绑定相同的key连接而不是在组件的构造函数中通过参数来进行传递。 以下示例通过Provide-Consume模式将更改从父组件传递到孙子组件。 Component struct LinkLinkChild {Consume Watch(testNumChange) testNum: number;testNumChange(propName: string): void {console.log(LinkLinkChild: testNum value ${this.testNum});}build() {Text(LinkLinkChild: ${this.testNum})} }Component struct PropLinkChild {Prop Watch(testNumChange) testNumGrand: number 0;testNumChange(propName: string): void {console.log(PropLinkChild: testNumGrand value ${this.testNumGrand});}build() {Text(PropLinkChild: ${this.testNumGrand}).height(70).backgroundColor(Color.Red).onClick(() {this.testNumGrand 1;})} }Component struct Sibling {Consume Watch(testNumChange) testNum: number;testNumChange(propName: string): void {console.log(Sibling: testNumChange value ${this.testNum});}build() {Text(Sibling: ${this.testNum})} }Component struct LinkChild {Consume Watch(testNumChange) testNum: number;testNumChange(propName: string): void {console.log(LinkChild: testNumChange value ${this.testNum});}build() {Column() {Button(incr testNum).onClick(() {console.log(LinkChild: before value change value ${this.testNum});this.testNum this.testNum 1console.log(LinkChild: after value change value ${this.testNum});})Text(LinkChild: ${this.testNum})LinkLinkChild({ /* empty */ })PropLinkChild({ testNumGrand: this.testNum })}.height(200).width(200)} }Entry Component struct Parent {Provide Watch(testNumChange1) testNum: number 1;testNumChange1(propName: string): void {console.log(Parent: testNumChange value ${this.testNum})}build() {Column() {LinkChild({ /* empty */ })Sibling({ /* empty */ })}} }给LocalStorage实例中对应的属性建立双向或单向同步 通过LocalStorageLink和LocalStorageProp给LocalStorage实例中的属性建立双向或单向同步。可以将LocalStorage实例视为State变量的Map。 LocalStorage对象可以在ArkUI应用程序的几个页面上共享。因此使用LocalStorageLink、LocalStorageProp和LocalStorage可以在应用程序的多个页面上共享状态。 以下示例中 创建一个LocalStorage实例并通过Entry(storage)将其注入根节点。 在Parent组件中初始化LocalStorageLink(“testNum”)变量时将在LocalStorage实例中创建testNum属性并设置指定的初始值为1即LocalStorageLink(“testNum”) testNum: number 1。 在其子组件中都使用LocalStorageLink或LocalStorageProp绑定同一个属性名key来传递数据。 LocalStorage可以被认为是State变量的Map属性名作为Map中的key。 LocalStorageLink和LocalStorage中对应的属性的同步行为和State和Link一致都为双向数据同步。 以下为组件的状态更新图 Component struct LinkLinkChild {LocalStorageLink(testNum) Watch(testNumChange) testNum: number 1;testNumChange(propName: string): void {console.log(LinkLinkChild: testNum value ${this.testNum});}build() {Text(LinkLinkChild: ${this.testNum})} }Component struct PropLinkChild {LocalStorageProp(testNum) Watch(testNumChange) testNumGrand: number 1;testNumChange(propName: string): void {console.log(PropLinkChild: testNumGrand value ${this.testNumGrand});}build() {Text(PropLinkChild: ${this.testNumGrand}).height(70).backgroundColor(Color.Red).onClick(() {this.testNumGrand 1;})} }Component struct Sibling {LocalStorageLink(testNum) Watch(testNumChange) testNum: number 1;testNumChange(propName: string): void {console.log(Sibling: testNumChange value ${this.testNum});}build() {Text(Sibling: ${this.testNum})} }Component struct LinkChild {LocalStorageLink(testNum) Watch(testNumChange) testNum: number 1;testNumChange(propName: string): void {console.log(LinkChild: testNumChange value ${this.testNum});}build() {Column() {Button(incr testNum).onClick(() {console.log(LinkChild: before value change value ${this.testNum});this.testNum this.testNum 1console.log(LinkChild: after value change value ${this.testNum});})Text(LinkChild: ${this.testNum})LinkLinkChild({ /* empty */ })PropLinkChild({ /* empty */ })}.height(200).width(200)} }// create LocalStorage object to hold the data const storage new LocalStorage(); Entry(storage) Component struct Parent {LocalStorageLink(testNum) Watch(testNumChange1) testNum: number 1;testNumChange1(propName: string): void {console.log(Parent: testNumChange value ${this.testNum})}build() {Column() {LinkChild({ /* empty */ })Sibling({ /* empty */ })}} }给AppStorage中对应的属性建立双向或单向同步 AppStorage是LocalStorage的单例对象ArkUI在应用程序启动时创建该对象在页面中使用StorageLink和StorageProp为多个页面之间共享数据具体使用方法和LocalStorage类似。 也可以使用PersistentStorage将AppStorage中的特定属性持久化到本地磁盘的文件中再次启动的时候StorageLink和StorageProp会恢复上次应用退出的数据。 示例如下 Component struct LinkLinkChild {StorageLink(testNum) Watch(testNumChange) testNum: number 1;testNumChange(propName: string): void {console.log(LinkLinkChild: testNum value ${this.testNum});}build() {Text(LinkLinkChild: ${this.testNum})} }Component struct PropLinkChild {StorageProp(testNum) Watch(testNumChange) testNumGrand: number 1;testNumChange(propName: string): void {console.log(PropLinkChild: testNumGrand value ${this.testNumGrand});}build() {Text(PropLinkChild: ${this.testNumGrand}).height(70).backgroundColor(Color.Red).onClick(() {this.testNumGrand 1;})} }Component struct Sibling {StorageLink(testNum) Watch(testNumChange) testNum: number 1;testNumChange(propName: string): void {console.log(Sibling: testNumChange value ${this.testNum});}build() {Text(Sibling: ${this.testNum})} }Component struct LinkChild {StorageLink(testNum) Watch(testNumChange) testNum: number 1;testNumChange(propName: string): void {console.log(LinkChild: testNumChange value ${this.testNum});}build() {Column() {Button(incr testNum).onClick(() {console.log(LinkChild: before value change value ${this.testNum});this.testNum this.testNum 1console.log(LinkChild: after value change value ${this.testNum});})Text(LinkChild: ${this.testNum})LinkLinkChild({ /* empty */})PropLinkChild({ /* empty */})}.height(200).width(200)} }Entry Component struct Parent {StorageLink(testNum) Watch(testNumChange1) testNum: number 1;testNumChange1(propName: string): void {console.log(Parent: testNumChange value ${this.testNum})}build() {Column() {LinkChild({ /* empty */})Sibling({ /* empty */})}} }ViewModel的嵌套场景 大多数情况下ViewModel数据项都是复杂类型的例如对象数组、嵌套对象或者这些类型的组合。对于嵌套场景可以使用Observed搭配Prop或者ObjectLink来观察变化。 Prop和ObjectLink嵌套数据结构 推荐设计单独的Component来渲染每一个数组或对象。此时对象数组或嵌套对象属性是对象的对象称为嵌套对象需要两个Component一个Component呈现外部数组/对象另一个Component呈现嵌套在数组/对象内的类对象。 Prop、Link、ObjectLink修饰的变量只能观察到第一层的变化。 对于类 可以观察到赋值的变化this.objnew ClassObj(…)可以观察到对象属性的更改this.obj.anew ClassA(…)不能观察更深层级的属性更改this.obj.a.b 47 对于数组 可以观察到数组的整体赋值this.arr[…]可以观察到数据项的删除、插入和替换this.arr[1] new ClassA()、this.arr.pop()、 this.arr.push(new ClassA(…))、this.arr.sort(…)不能观察更深层级的数组变化this.arr[1].b 47 如果要观察嵌套类的内部对象的变化可以使用ObjectLink或Prop。优先考虑ObjectLink其通过嵌套对象内部属性的引用初始化自身。Prop会对嵌套在内部的对象的深度拷贝来进行初始化以实现单向同步。在性能上Prop的深度拷贝比ObjectLink的引用拷贝慢很多。 ObjectLink或Prop可以用来存储嵌套内部的类对象该类必须用Observed类装饰器装饰否则类的属性改变并不会触发更新UI并不会刷新。Observed为其装饰的类实现自定义构造函数此构造函数创建了一个类的实例并使用ES6代理包装由ArkUI框架实现拦截修饰class属性的所有“get”和“set”。“set”观察属性值当发生赋值操作时通知ArkUI框架更新。“get”收集哪些UI组件依赖该状态变量实现最小化UI更新。 如果嵌套场景中嵌套数据内部是数组或者class时需根据以下场景使用Observed类装饰器。 如果嵌套数据内部是class直接被Observed装饰。 如果嵌套数据内部是数组可以通过以下方式来观察数组变化。 Observed class ObservedArrayT extends ArrayT {constructor(args: T[]) {if (args instanceof Array) {super(...args);} else {super(args)}}/* otherwise empty */ }ViewModel为外层class。 class Outer {innerArrayProp : ObservedArraystring [];... }嵌套数据结构中Prop和ObjectLink之的区别 以下示例中 父组件ViewB渲染State arrAArrayClassA。State可以观察新数组的分配、数组项插入、删除和替换。 子组件ViewA渲染每一个ClassA的对象。 类装饰器Observed ClassA与ObjectLink a: ClassA。 可以观察嵌套在Array内的ClassA对象的变化。 不使用Observed时 ViewB中的this.arrA[Math.floor(this.arrA.length/2)].c10将不会被观察到相应的ViewA组件也不会更新。 对于数组中的第一个和第二个数组项每个数组项都初始化了两个ViewA的对象渲染了同一个ViewA实例。在一个ViewA中的属性赋值this.a.c 1;时不会引发另外一个使用同一个ClassA初始化的ViewA的渲染更新。 let NextID: number 1;// 类装饰器Observed装饰ClassA Observed class ClassA {public id: number;public c: number;constructor(c: number) {this.id NextID;this.c c;} }Component struct ViewA {ObjectLink a: ClassA;label: string ViewA1;build() {Row() {Button(ViewA [${this.label}] this.a.c ${this.a.c} 1).onClick(() {// 改变对象属性this.a.c 1;})}} }Entry Component struct ViewB {State arrA: ClassA[] [new ClassA(0), new ClassA(0)];build() {Column() {ForEach(this.arrA,(item: ClassA) {ViewA({ label: #${item.id}, a: item })},(item: ClassA): string { return item.id.toString(); })Divider().height(10)if (this.arrA.length) {ViewA({ label: ViewA this.arrA[first], a: this.arrA[0] })ViewA({ label: ViewA this.arrA[last], a: this.arrA[this.arrA.length-1] })}Divider().height(10)Button(ViewB: reset array).onClick(() {// 替换整个数组会被State this.arrA观察到this.arrA [new ClassA(0), new ClassA(0)];})Button(array push).onClick(() {// 数组中插入数据会被State this.arrA观察到this.arrA.push(new ClassA(0))})Button(array shift).onClick(() {// 数组中移除数据会被State this.arrA观察到this.arrA.shift()})Button(ViewB: chg item property in middle).onClick(() {// 替换数组中的某个元素会被State this.arrA观察到this.arrA[Math.floor(this.arrA.length / 2)] new ClassA(11);})Button(ViewB: chg item property in middle).onClick(() {// 改变数组中某个元素的属性c会被ViewA中的ObjectLink观察到this.arrA[Math.floor(this.arrA.length / 2)].c 10;})}} }在ViewA中将ObjectLink替换为Prop。 Component struct ViewA {Prop a: ClassA new ClassA(0);label : string ViewA1;build() {Row() {Button(ViewA [${this.label}] this.a.c ${this.a.c} 1).onClick(() {// change object propertythis.a.c 1;})}} }与用Prop修饰不同用ObjectLink修饰时点击数组的第一个或第二个元素后面两个ViewA会发生同步的变化。 Prop是单向数据同步ViewA内的Button只会触发Button自身的刷新不会传播到其他的ViewA实例中。在ViewA中的ClassA只是一个副本并不是其父组件中State arrA : ArrayClassA中的对象也不是其他ViewA的ClassA这使得数组的元素和ViewA中的元素表面是传入的同一个对象实际上在UI上渲染使用的是两个互不相干的对象。 需要注意Prop和ObjectLink还有一个区别ObjectLink装饰的变量是仅可读的不能被赋值Prop装饰的变量可以被赋值。 ObjectLink实现双向同步因为它是通过数据源的引用初始化的。 Prop是单向同步需要深拷贝数据源。 对于Prop赋值新的对象就是简单地将本地的值覆写但是对于实现双向数据同步的ObjectLink覆写新的对象相当于要更新数据源中的数组项或者class的属性这个对于 TypeScript/JavaScript是不能实现的。 MVVM应用示例 以下示例深入探讨了嵌套ViewModel的应用程序设计特别是自定义组件如何渲染一个嵌套的Object该场景在实际的应用开发中十分常见。 开发一个电话簿应用实现功能如下 显示联系人和设备“Me”电话号码 。 选中联系人时进入可编辑态“Edit”可以更新该联系人详细信息包括电话号码住址。 在更新联系人信息时只有在单击保存“Save Changes”之后才会保存更改。 可以点击删除联系人“Delete Contact”可以在联系人列表删除该联系人。 ViewModel需要包括 AddressBookclass me设备: 存储一个Person类。contacts设备联系人存储一个Person类数组。 AddressBook类声明如下 export class AddressBook {me: Person;contacts: ObservedArrayPerson;constructor(me: Person, contacts: Person[]) {this.me me;this.contacts new ObservedArrayPerson(contacts);} }Person (class) name : stringaddress : Addressphones: ObservedArraystringAddress (class) street : stringzip : numbercity : string Address类声明如下 Observed export class Address {street: string;zip: number;city: string;constructor(street: string,zip: number,city: string) {this.street street;this.zip zip;this.city city;} }Person类声明如下 let nextId 0;Observed export class Person {id_: string;name: string;address: Address;phones: ObservedArraystring;constructor(name: string,street: string,zip: number,city: string,phones: string[]) {this.id_ ${nextId};nextId;this.name name;this.address new Address(street, zip, city);this.phones new ObservedArraystring(phones);} }需要注意的是因为phones是嵌套属性如果要观察到phones的变化需要extends array并用Observed修饰它。ObservedArray类的声明如下。 Observed export class ObservedArrayT extends ArrayT {constructor(args: T[]) {console.log(ObservedArray: ${JSON.stringify(args)} )if (args instanceof Array) {super(...args);} else {super(args)}} }selected : 对Person的引用。 更新流程如下 在根节点PageEntry中初始化所有的数据将me和contacts和其子组件AddressBookView建立双向数据同步selectedPerson默认为me需要注意selectedPerson并不是PageEntry数据源中的数据而是数据源中对某一个Person的引用。 PageEntry和AddressBookView声明如下 Component struct AddressBookView {ObjectLink me : Person;ObjectLink contacts : ObservedArrayPerson;State selectedPerson: Person new Person(, , 0, , []);aboutToAppear() {this.selectedPerson this.me;}build() {Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start}) {Text(Me:)PersonView({person: this.me,phones: this.me.phones,selectedPerson: this.selectedPerson})Divider().height(8)ForEach(this.contacts, (contact: Person) {PersonView({person: contact,phones: contact.phones as ObservedArraystring,selectedPerson: this.selectedPerson})},(contact: Person): string { return contact.id_; })Divider().height(8)Text(Edit:)PersonEditView({ selectedPerson: this.selectedPerson, name: this.selectedPerson.name, address: this.selectedPerson.address, phones: this.selectedPerson.phones })}.borderStyle(BorderStyle.Solid).borderWidth(5).borderColor(0xAFEEEE).borderRadius(5)} }Entry Component struct PageEntry {Provide addrBook: AddressBook new AddressBook(new Person(Gigi, Itamerenkatu 9, 180, Helsinki, [18*********, 18*********, 18*********]),[new Person(Oly, Itamerenkatu 9, 180, Helsinki, [18*********, 18*********]),new Person(Sam, Itamerenkatu 9, 180, Helsinki, [18*********, 18*********]),new Person(Vivi, Itamerenkatu 9, 180, Helsinki, [18*********, 18*********]),]);build() {Column() {AddressBookView({ me: this.addrBook.me, contacts: this.addrBook.contacts, selectedPerson: this.addrBook.me })}} }PersonView即电话簿中联系人姓名和首选电话的View当用户选中即高亮当前Person需要同步回其父组件AddressBookView的selectedPerson所以需要通过Link建立双向同步。 PersonView声明如下 // 显示联系人姓名和首选电话 // 为了更新电话号码这里需要ObjectLink person和ObjectLink phones // 显示首选号码不能使用this.person.phones[0]因为ObjectLink person只代理了Person的属性数组内部的变化观察不到 // 触发onClick事件更新selectedPerson Component struct PersonView {ObjectLink person : Person;ObjectLink phones : ObservedArraystring;Link selectedPerson : Person;build() {Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {Text(this.person.name)if (this.phones.length 0) {Text(this.phones[0])}}.height(55).backgroundColor(this.selectedPerson.name this.person.name ? #ffa0a0 : #ffffff).onClick(() {this.selectedPerson this.person;})} }选中的Person会在PersonEditView中显示详细信息对于PersonEditView的数据同步分为以下三种方式 在Edit状态通过Input.onChange回调事件接受用户的键盘输入时在点击“Save Changes”之前这个修改是不希望同步回数据源的但又希望刷新在当前的PersonEditView中所以Prop深拷贝当前Person的详细信息 PersonEditView通过Link seletedPerson: Person和AddressBookView的selectedPerson建立双向同步当用户点击“Save Changes”的时候Prop的修改将被赋值给Link seletedPerson: Person这就意味这数据将被同步回数据源。 PersonEditView中通过Consume addrBook: AddressBook和根节点PageEntry建立跨组件层级的直接的双向同步关系当用户在PersonEditView界面删除某一个联系人时会直接同步回PageEntryPageEntry的更新会通知AddressBookView刷新contracts的列表页。 PersonEditView声明如下 // 渲染Person的详细信息// Prop装饰的变量从父组件AddressBookView深拷贝数据将变化保留在本地, TextInput的变化只会在本地副本上进行修改。// 点击 Save Changes 会将所有数据的复制通过Prop到Link, 同步到其他组件Componentstruct PersonEditView {Consume addrBook : AddressBook;/* 指向父组件selectedPerson的引用 */Link selectedPerson: Person;/*在本地副本上编辑直到点击保存*/Prop name: string ;Prop address : Address new Address(, 0, );Prop phones : ObservedArraystring [];selectedPersonIndex() : number {return this.addrBook.contacts.findIndex((person: Person) person.id_ this.selectedPerson.id_);}build() {Column() {TextInput({ text: this.name}).onChange((value) {this.name value;})TextInput({text: this.address.street}).onChange((value) {this.address.street value;})TextInput({text: this.address.city}).onChange((value) {this.address.city value;})TextInput({text: this.address.zip.toString()}).onChange((value) {const result Number.parseInt(value);this.address.zip Number.isNaN(result) ? 0 : result;})if (this.phones.length 0) {ForEach(this.phones,(phone: ResourceStr, index?:number) {TextInput({ text: phone }).width(150).onChange((value) {console.log(${index}. ${value} value has changed)this.phones[index!] value;})},(phone: ResourceStr, index?:number) ${index})}Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {Text(Save Changes).onClick(() {// 将本地副本更新的值赋值给指向父组件selectedPerson的引用// 避免创建新对象在现有属性上进行修改this.selectedPerson.name this.name;this.selectedPerson.address new Address(this.address.street, this.address.zip, this.address.city)this.phones.forEach((phone : string, index : number) { this.selectedPerson.phones[index] phone } );})if (this.selectedPersonIndex()!-1) {Text(Delete Contact).onClick(() {let index this.selectedPersonIndex();console.log(delete contact at index ${index});// 删除当前联系人this.addrBook.contacts.splice(index, 1);// 删除当前selectedPerson选中态前移一位index (index this.addrBook.contacts.length) ? index : index-1;// 如果contract被删除完则设置me为选中态this.selectedPerson (index0) ? this.addrBook.contacts[index] : this.addrBook.me;})}}}}}其中关于ObjectLink和Link的区别要注意以下几点 在AddressBookView中实现和父组件PageView的双向同步需要用ObjectLink me : Person和ObjectLink contacts : ObservedArrayPerson而不能用Link原因如下 - Link需要和其数据源类型完全相同且仅能观察到第一层的变化 - ObjectLink可以被数据源的属性初始化且代理了Observed装饰类的属性可以观察到被装饰类属性的变化。当 联系人姓名 (Person.name) 或者首选电话号码 (Person.phones[0]) 发生更新时PersonView也需要同步刷新其中Person.phones[0]属于第二层的更新如果使用Link将无法观察到而且Link需要和其数据源类型完全相同。所以在PersonView中也需要使用ObjectLink即ObjectLink person : Person和ObjectLink phones : ObservedArraystring。 在这个例子中我们可以大概了解到如何构建ViewModel在应用的根节点中ViewModel的数据可能是可以巨大的嵌套数据但是在ViewModel和View的适配和渲染中我们尽可能将ViewModel的数据项和View相适配这样的话在针对每一层的View都是一个相对“扁平”的数据仅观察当前层就可以了。 在应用实际开发中也许我们无法避免去构建一个十分庞大的Model但是我们可以在UI树状结构中合理地去拆分数据使得ViewModel和View更好的适配从而搭配最小化更新来实现高性能开发。 完整应用代码如下 // ViewModel classes let nextId 0;Observed export class ObservedArrayT extends ArrayT {constructor(args: T[]) {console.log(ObservedArray: ${JSON.stringify(args)} )if (args instanceof Array) {super(...args);} else {super(args)}} }Observed export class Address {street: string;zip: number;city: string;constructor(street: string,zip: number,city: string) {this.street street;this.zip zip;this.city city;} }Observed export class Person {id_: string;name: string;address: Address;phones: ObservedArraystring;constructor(name: string,street: string,zip: number,city: string,phones: string[]) {this.id_ ${nextId};nextId;this.name name;this.address new Address(street, zip, city);this.phones new ObservedArraystring(phones);} }export class AddressBook {me: Person;contacts: ObservedArrayPerson;constructor(me: Person, contacts: Person[]) {this.me me;this.contacts new ObservedArrayPerson(contacts);} }// 渲染出Person对象的名称和Observed数组string中的第一个号码 // 为了更新电话号码这里需要ObjectLink person和ObjectLink phones // 不能使用this.person.phones内部数组的更改不会被观察到。 // 在AddressBookView、PersonEditView中的onClick更新selectedPerson Component struct PersonView {ObjectLink person: Person;ObjectLink phones: ObservedArraystring;Link selectedPerson: Person;build() { Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {Text(this.person.name)if (this.phones.length) {Text(this.phones[0])}}.height(55).backgroundColor(this.selectedPerson.name this.person.name ? #ffa0a0 : #ffffff).onClick(() {this.selectedPerson this.person;})} }Component struct phonesNumber {ObjectLink phoneNumber: ObservedArraystringbuild() {Column() {ForEach(this.phoneNumber,(phone: ResourceStr, index?: number) {TextInput({ text: phone }).width(150).onChange((value) {console.log(${index}. ${value} value has changed)this.phoneNumber[index!] value;})},(phone: ResourceStr, index: number) ${this.phoneNumber[index] index})}} }// 渲染Person的详细信息 // Prop装饰的变量从父组件AddressBookView深拷贝数据将变化保留在本地, TextInput的变化只会在本地副本上进行修改。 // 点击 Save Changes 会将所有数据的复制通过Prop到Link, 同步到其他组件 Component struct PersonEditView {Consume addrBook: AddressBook;/* 指向父组件selectedPerson的引用 */Link selectedPerson: Person;/*在本地副本上编辑直到点击保存*/Prop name: string ;Prop address: Address new Address(, 0, );Prop phones: ObservedArraystring [];selectedPersonIndex(): number {return this.addrBook.contacts.findIndex((person: Person) person.id_ this.selectedPerson.id_);}build() {Column() {TextInput({ text: this.name }).onChange((value) {this.name value;})TextInput({ text: this.address.street }).onChange((value) {this.address.street value;})TextInput({ text: this.address.city }).onChange((value) {this.address.city value;})TextInput({ text: this.address.zip.toString() }).onChange((value) {const result Number.parseInt(value);this.address.zip Number.isNaN(result) ? 0 : result;})if (this.phones.length 0) {phonesNumber({ phoneNumber: this.phones })}Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {Text(Save Changes).onClick(() {// 将本地副本更新的值赋值给指向父组件selectedPerson的引用// 避免创建新对象在现有属性上进行修改this.selectedPerson.name this.name;this.selectedPerson.address new Address(this.address.street, this.address.zip, this.address.city)this.phones.forEach((phone: string, index: number) {this.selectedPerson.phones[index] phone});})if (this.selectedPersonIndex() ! -1) {Text(Delete Contact).onClick(() {let index this.selectedPersonIndex();console.log(delete contact at index ${index});// 删除当前联系人this.addrBook.contacts.splice(index, 1);// 删除当前selectedPerson选中态前移一位index (index this.addrBook.contacts.length) ? index : index - 1;// 如果contract被删除完则设置me为选中态this.selectedPerson (index 0) ? this.addrBook.contacts[index] : this.addrBook.me;})}}}} }Component struct AddressBookView {ObjectLink me: Person;ObjectLink contacts: ObservedArrayPerson;State selectedPerson: Person new Person(, , 0, , []);aboutToAppear() {this.selectedPerson this.me;}build() {Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start }) {Text(Me:)PersonView({person: this.me,phones: this.me.phones,selectedPerson: this.selectedPerson})Divider().height(8)ForEach(this.contacts, (contact: Person) {PersonView({person: contact,phones: contact.phones as ObservedArraystring,selectedPerson: this.selectedPerson})},(contact: Person): string {return contact.id_;})Divider().height(8)Text(Edit:)PersonEditView({selectedPerson: this.selectedPerson,name: this.selectedPerson.name,address: this.selectedPerson.address,phones: this.selectedPerson.phones})}.borderStyle(BorderStyle.Solid).borderWidth(5).borderColor(0xAFEEEE).borderRadius(5)} }Entry Component struct PageEntry {Provide addrBook: AddressBook new AddressBook(new Person(Gigi, Itamerenkatu 9, 180, Helsinki, [18*********, 18*********, 18*********]),[new Person(Oly, Itamerenkatu 9, 180, Helsinki, [11*********, 12*********]),new Person(Sam, Itamerenkatu 9, 180, Helsinki, [13*********, 14*********]),new Person(Vivi, Itamerenkatu 9, 180, Helsinki, [15*********, 168*********]),]);build() {Column() {AddressBookView({me: this.addrBook.me,contacts: this.addrBook.contacts,selectedPerson: this.addrBook.me})}} }为了能让大家更好的学习鸿蒙HarmonyOS NEXT开发技术这边特意整理了《鸿蒙开发学习手册》共计890页希望对大家有所帮助https://qr21.cn/FV7h05 《鸿蒙开发学习手册》 如何快速入门https://qr21.cn/FV7h05 基本概念构建第一个ArkTS应用…… 开发基础知识https://qr21.cn/FV7h05 应用基础知识配置文件应用数据管理应用安全管理应用隐私保护三方应用调用管控机制资源分类与访问学习ArkTS语言…… 基于ArkTS 开发https://qr21.cn/FV7h05 Ability开发UI开发公共事件与通知窗口管理媒体安全网络与链接电话服务数据管理后台任务(Background Task)管理设备管理设备使用信息统计DFX国际化开发折叠屏系列…… 鸿蒙开发面试真题含参考答案https://qr18.cn/F781PH 鸿蒙开发面试大盘集篇共计319页https://qr18.cn/F781PH 1.项目开发必备面试题 2.性能优化方向 3.架构方向 4.鸿蒙开发系统底层方向 5.鸿蒙音视频开发方向 6.鸿蒙车载开发方向 7.鸿蒙南向开发方向
http://www.zqtcl.cn/news/308002/

相关文章:

  • 网站开发比较厉害wordpress中文 插件
  • 文化投资的微网站怎么做个人微信公众号如何推广
  • 单位的网站怎样设计才美观网页设计图片的代码
  • 长沙专业做网站排名济南手机网站定制费用
  • 西安专题门户响应式网站建设系统网站有哪些
  • 山东省建设局网站监理员考试asp.net mvc6电商网站开发实践
  • 做网站需要提供什么资料网站备案是什么意思
  • 河南网站建设及推广东莞百度代做网站联系方式
  • 大型企业网站制作浦东新区做网站
  • 简单大气网站源码织梦怎么用框架实现在浏览器的地址栏只显示网站的域名而不显示出文件名
  • 电子商务型网站建设线上推广营销策划
  • 网站建设管理工作情况的通报网站开发vs设计报告
  • 嘉定网站网站建设公司官网制作
  • 做旅游广告在哪个网站做效果好财经网站建设
  • 网站样式下载网站地图定位用什么技术做
  • 自己做网站怎么做的百中搜优化软件
  • 南宁建站平台与网站建设相关的论文题目
  • 足球网站建设意义做股权众筹的网站
  • 北京网站建设设计一流的锦州网站建设
  • 专业手机移动网站建设什么网站可以做期刊封面
  • cms建站系统哪个好网站建设 柳州
  • 安徽省住房与城乡建设部网站八戒电影在线观看免费7
  • 江苏省建设考试网站准考证打印佛山网站建设锐艺a068
  • 展示型网站功能如何设计网站风格
  • wordpress图床网站网站什么时候做等保
  • 怎么创办网站浅谈博物馆网站建设的意义
  • 如何做擦边球网站网站seo规划
  • 建站知乎做网站销售工资
  • 仙居住房和城乡建设局网站用手机看网站源代码
  • 网架加工厂家seo关键词优化推广报价表