🎯 : MVVM V2状态管理 开发模式,看完即懂!

⭐⭐⭐⭐⭐⭐

📢 前言

📖 项目中用到的V2的装饰器相关的讲解楼主都有博文输出

🔥🔥🔥 实战环节-基础列表(点赞、删除、添加)开发

🧱 MVVM模式组织结构

├── src
│   ├── ets
│   │   ├── components
│   │   │   ├──model
│   │   │   ├── ListItemModel.ets
│   │   │   └── ListModel.ets
│   │   │   ├──views
│   │   │   ├── ListComponent.ets
│   │   │   └── ListItemComponent.ets
│   │   │   ├──viewModel
│   │   │   ├── ListItemViewModel.ets
│   │   │   └── ListViewModel.ets
│   │   ├── pages
│   │   │   ├── Index.ets
│   └── resources
│   │   ├── rawfile
│   │   │   ├── listsData.json

🧱 model

// model/ListItemModel.ets
/**
 * 接口每项的字段
 */
export class ListItemModel {
  label: string = ''
  collect: boolean = false
}

// model/ListModel.ets
/**
 * 提供原始数据和接口API设定
 */
import { common } from '@kit.AbilityKit'
import { util } from '@kit.ArkTS'
import { ListItemModel } from './ListItemModel'

export class ListModel {
  lists: Array<ListItemModel> = []

  async getMockData(context: common.UIAbilityContext) {
    const getJson = await context.resourceManager.getRawFileContent('listsData.json')
    const textDecoderOptions: util.TextDecoderOptions = { ignoreBOM: true }
    const textDecoder = util.TextDecoder.create('utf-8', textDecoderOptions)
    const result = textDecoder.decodeToString(getJson, { stream: false })
    this.lists = JSON.parse(result)
  }
}

🧱 views

// views/ListItemComponent.ets
/**
 * 列表项(item)
 */
import ListItemViewModel from '../viewModel/ListItemViewModel';

@ComponentV2
export struct ListItemComponent {
  @Require @Param itemData: ListItemViewModel = new ListItemViewModel()
  @Event removeOneself: () => void = () => {}

  build() {
    Row() {
      Text(this.itemData.label)
      Blank()
      Row({ space: 10 }) {
        Text('删除')
          .fontSize(20)
          .fontColor(Color.White)
          .backgroundColor(Color.Red)
          .padding({
            left: 3,
            right: 3,
            top: 3,
            bottom: 3
          })
          .borderRadius(5)
          .onClick(() => this.removeOneself())
        Text()
          .width(50)
          .aspectRatio(1)
          .borderRadius('50%')
          .backgroundColor(this.itemData.collect ? '#ff4cd1d6' : Color.Transparent)
          .borderWidth(1)
          .borderColor(Color.Black)
          .onClick(() => this.itemData.updateIsCollect(this.getUIContext()))
      }
    }
    .width('100%')
    .height(80)
    .padding({ left: 40, right: 20 })
    .borderWidth(1)
    .borderColor(Color.Black)
    .borderRadius(12)
  }
}

// views/ListComponent.ets
/**
 * 列表循环
 */
import { common } from "@kit.AbilityKit"
import ListViewModel from "../viewModel/ListViewModel"
import ListItemViewModel from "../viewModel/ListItemViewModel"
import { ListItemComponent } from "./ListItemComponent"

@ComponentV2
export struct ListComponent {
  private context = this.getUIContext().getHostContext() as common.UIAbilityContext
  @Local listViewModel: ListViewModel = new ListViewModel()

  async aboutToAppear() {
    await this.listViewModel.getMockData(this.context)
  }

  build() {
    Column({ space: 10 }) {
      Text('点击:添加固定数据 (后羿)')
        .fontSize(20)
        .fontColor(Color.White)
        .backgroundColor(Color.Green)
        .padding({
          left: 3,
          right: 3,
          top: 3,
          bottom: 3
        })
        .borderRadius(5)
        .onClick(() => this.listViewModel.addItemData())

      Column({ space: 4 }) {
        Repeat<ListItemViewModel>(this.listViewModel.lists)
          .each((obj: RepeatItem<ListItemViewModel>) => {
            ListItemComponent({
              itemData: obj.item,
              removeOneself: () => {
                this.listViewModel.removeItemData(obj.item)
              }
            })
          })
      }
      .width('100%')
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Start)

  }
}

🧱 viewModel

// viewModel/ListItemViewModel.ets
/**
 * 列表(item)自身的事件定义
 */
import { ListItemModel } from "../model/ListItemModel"

@ObservedV2
export default class ListItemViewModel {
  @Trace label: string = ''
  @Trace collect: boolean = false

  setItemData(item: ListItemModel) {
    this.label = item.label
    this.collect = item.collect
  }

  updateIsCollect(uiContext: UIContext): void {
    this.collect = !this.collect
    uiContext.getPromptAction().showToast({ message: this.collect ? '收藏成功' : '取消收藏' })
  }
}

// viewModel/ListViewModel.ets
/**
 * 中介层提供处理的好数据给视图展示
 */
import ListItemViewModel from "./ListItemViewModel"
import { common } from "@kit.AbilityKit"
import { ListModel } from "../model/ListModel"
import { PersistenceV2, promptAction, Type, UIUtils } from "@kit.ArkUI"

// 持久化失败时调用
PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
  console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`);
})

@ObservedV2
export default class ListViewModel {
  @Type(ListItemViewModel)
  @Trace lists: Array<ListItemViewModel> = []

  // TODO: 有个弊端类被实例化的时候会触发一次,想要解决可以使用 API 20+ addMonitor 这个方法动态添加
  @Monitor('lists.length')
  listsLengthChange(monitor: IMonitor) {
    monitor.dirty.forEach((path: string) => {
      if (monitor.value(path)?.before !== monitor.value(path)?.now) {
        promptAction.showToast({ message: `当前英雄数量${this.lists.length}`})
      }
    })
  }

  // TODO: 这里是配合下方addMonitor的使用方式
  // listsLengthChange(monitor: IMonitor) {
  //   monitor.dirty.forEach((path: string) => {
  //     if (monitor.value(path)?.before !== monitor.value(path)?.now) {
  //       promptAction.showToast({ message: `当前英雄数量${this.lists.length}`})
  //     }
  //   })
  // }


  async getMockData(context: common.UIAbilityContext) {
    this.lists = PersistenceV2.connect(ListViewModel, 'ListViewModel', () => new ListViewModel())!.lists
    if (this.lists.length <= 0) {
      const result = new ListModel()
      await result.getMockData(context)
      for (let item of result.lists) {
        let listItemViewModel = new ListItemViewModel()
        listItemViewModel.setItemData(item)
        this.lists.push(listItemViewModel)
      }
      PersistenceV2.connect(ListViewModel, 'ListViewModel', () => new ListViewModel())!
    }
    // TODO:配合上方的使用方式
    // UIUtils.addMonitor(this, 'lists.length', this.listsLengthChange)
  }

  addItemData() {
    let listItemViewModel = new ListItemViewModel()
    listItemViewModel.label = '后羿'
    listItemViewModel.collect = false
    this.lists.unshift(listItemViewModel)
  }

  removeItemData(removedItem: ListItemViewModel) {
    this.lists.splice(this.lists.indexOf(removedItem), 1)
  }
}

🧱 listsData

// src/main/resources/rawfile/listsData.json
/**
 * mock字段
 * label 标题
 * collect 收藏
 */
[
  {"label": "后羿", "collect": false},
  {"label": "孙尚香", "collect": false},
  {"label": "黄忠", "collect": false},
  {"label": "马可波罗", "collect": false},
  {"label": "公孙离", "collect": false},
  {"label": "狄仁杰", "collect": false},
  {"label": "敖丙", "collect": false}
]

🎉 成果图

🌍️ 前往gitee仓库

链接

📝 V1过渡到V2爽点

@Observed
export default class ListViewModel {
@Track lists: ListViewModelArray = new ListViewModelArray()
}

V2
@ObservedV2
export default class ListViewModel {
@Trace lists: Array = []
}

```

🌸🌼🌺

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐