HarmonyOS MVVM模式,​viewmodel怎么和view建立关系的?

​viewmodel怎么和view建立关系的。

viewmodel数据变化的时候怎么通知view中的装饰器的?

应用示例电话簿的demo。​


HarmonyOS
2024-11-28 09:27:20
1158浏览
收藏 0
回答 2
回答 2
按赞同
/
按时间
因为活着就一定行

在HarmonyOS的MVVM模式中,ViewModel与View的建立关系主要是通过数据绑定来实现的。ViewModel层负责管理UI状态和交互逻辑,它监控Model数据的变化,并在数据变化时通知View更新UI。


这种模式允许View和Model之间通过ViewModel进行解耦,确保View只负责展示数据,而Model只负责数据管理。

在ArkUI框架中,ViewModel通过装饰器如​@State​​​、​​@Link​​​等来定义状态变量。这些状态变量通常作为父组件的数据源,当状态变量更新时,UI会自动刷新。例如,使用​​@State​​装饰器定义的一个状态变量,可以在View中通过绑定这个变量来动态更新UI组件,如文本、按钮等。



一个示例:电话簿应用虽然具体的电话簿Demo代码不在提供的信息中,但可以想象在一个电话簿应用中,ViewModel可能会管理一组联系人数据,并提供方法来添加、编辑和删除联系人。这些操作会通过状态变量反映出来,例如一个​​contacts​​​数组。当ViewModel中的​​contacts​​数组更新时,绑定这个数组的View组件,如列表或表格,会自动反映新的数据状态。

@Entry
@Component
struct ContactList {
    @State contacts: Contact[] = []; // 假设Contact是一个接口,定义了联系人的属性

    build() {
        ListView({
            items: this.contacts,
            itemBuilder: (contact) => {
                Text(contact.name)
                .fontSize(16)
                .onClick(() => {
                    // 编辑联系人逻辑
                });
            }
        });
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

在这个示例中,​​contacts​​是一个状态变量,当其内容变化时,ListView会自动更新其展示的UI。

通过这种方式,ViewModel和View在HarmonyOS中建立了紧密的关系,使得数据驱动的UI更新成为可能,同时也保持了高层级的解耦,使得应用更加模块化和易于维护。

已于2024-11-28 11:38:58修改
分享
微博
QQ
微信
回复
2024-11-28 11:37:39
zbw_apple

​被@State修饰的变量在被更新后因为会重新触发UI渲染,也就是会重新执行build方法,所以页面会实时显示更新的数据。@Provide也具有@State的特性,同时@Provide可以与@Consume搭配实现父、子和后代组件数据同步。关于这些装饰器的内容,可在官网文档中进行详细了解:​https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-component-state-management-V5

应用示例电话簿的demo如下​:

//Index.ets 
import { Person, Address, AddressBook, ObservedArray} from '../viewmodel/ListViewModel' 
import emitter from '@ohos.events.emitter'; 
 
// 渲染出Person对象的名称和Observed数组<string>中的第一个号码 
// 为了更新电话号码,这里需要@ObjectLink person和@ObjectLink phones, 
// 不能使用this.person.phones,内部数组的更改不会被观察到。 
// 在AddressBookView、PersonEditView中的onClick更新selectedPerson 
@Component 
struct PersonView { 
  @ObjectLink person: Person; 
  @ObjectLink phones: ObservedArray<string>; 
  @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: ObservedArray<string> 
 
  build() { 
    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: ObservedArray<string> = []; 
 
  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 
            }); 
            emitter.emit('setModelData', { 
              data: { 
                id: this.selectedPerson.id_, 
                name: this.name 
              } 
            }) 
          }) 
        Button('getData') 
          .onClick(() => { 
            emitter.emit('getModelData'); 
            this.selectedPerson = this.addrBook.me; 
          }) 
        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: ObservedArray<Person>; 
  @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 ObservedArray<string>, 
          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 Index { 
  @StorageLink("contacts") @Watch("contactsChange") contacts: Array<Person> = []; 
  @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*********"]), 
    ]); 
 
  contactsChange(propName: string): void { 
    this.addrBook.contacts = AppStorage.get(propName) as ObservedArray<Person>; 
  } 
 
 
  build() { 
    Column() { 
      AddressBookView({ 
        me: this.addrBook.me, 
        contacts: this.addrBook.contacts, 
        selectedPerson: this.addrBook.me 
      }) 
    } 
  } 
}
//viewmodel->ListViewModel.ets 
import emitter from '@ohos.events.emitter'; 
import { Person } from '../viewmodel/ListViewModel'; 
 
const data = [ 
  new Person("Oly1", "Itamerenkatu 9", 180, "Helsinki", ["11*********", "12*********"]), 
  new Person("Sam1", "Itamerenkatu 9", 180, "Helsinki", ["13*********", "14*********"]), 
  new Person("Vivi1", "Itamerenkatu 9", 180, "Helsinki", ["15*********", "168*********"]), 
]; 
 
export function init() { 
  emitter.on('getModelData', () => { 
    AppStorage.setOrCreate('contacts', data) 
  }) 
 
  emitter.on('setModelData', (eventData) => { 
    if (eventData.data) { 
      const changeId: string = eventData.data['id']; 
      data.forEach((person => { 
        if (person.id_ === changeId) { 
          person.name = eventData.data!['name']; 
        } 
      })) 
      AppStorage.setOrCreate('contacts', data) 
    } 
  }) 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.

viewmode和model通过状态变量进行数据更新后,viewmodel再通过状态变量的更新来触发view页面的build方法和@Builder自定义构建函数的执行;build方法和@Builder自定义构建函数的执行又会使相应的状态变量改变,如此实现“更新数据的时候直接更新视图”。

分享
微博
QQ
微信
回复
2024-11-28 14:58:32
相关问题
MVVM模式ViewModel层指的是什么?
980浏览 • 1回复 待解决
如何理解ArkUI中MVVM模式
1562浏览 • 1回复 待解决
HarmonyOS UIViewModel如何刷新数据
701浏览 • 1回复 待解决
HarmonyOS 怎么使用webview建立数据通信
946浏览 • 1回复 待解决
HarmonyOS 图片像素屏幕尺寸关系
610浏览 • 1回复 待解决
网络请求rcphttpRequest关系区别
1392浏览 • 1回复 待解决
HarmonyOS hilogconsole.log关系
882浏览 • 1回复 待解决
怎么理解HAR、HAP、App关系
2930浏览 • 1回复 待解决
鸿蒙 LiteOS 啥关系?
14403浏览 • 1回复 待解决
HarmonyOS 自定义View怎么转换为PixelMap
616浏览 • 1回复 待解决
HarmonyOS 有类似mvvm或mvc框架么
871浏览 • 1回复 待解决