MVVM V2状态管理 开发模式,看完即懂!
·
🎯 : MVVM V2状态管理 开发模式,看完即懂!
⭐⭐⭐⭐⭐⭐
📢 前言
📖 项目中用到的V2的装饰器相关的讲解楼主都有博文输出
-
MVVM V1 开发模式: 相关MVVM模式讲解在这一篇V1的文章输出过
-
@Local: 浅入@Local装饰器: 组件内部状态
-
@Event: 浅入@Event装饰器: 规范组件输出
-
@Monitor: 浅入@Monitor装饰器: 状态变量修改监听
-
PersistenceV2: 浅入PersistenceV2: 持久化储存UI状态
-
Repeat: 浅入Repeat: 子组件复用
🔥🔥🔥 实战环节-基础列表(点赞、删除、添加)开发
🧱 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爽点
-
V1例子ListViewModel,里面的lists字段赋值必须要使用@Observed装饰的类型,不然在视图层显示不出数据
-
V2新增或者优化的装饰器让逻辑以及结构上更加清晰明了,易追溯
```javascript
V1
@Observed
export class ListViewModelArray extends Array {
}
@Observed
export default class ListViewModel {
@Track lists: ListViewModelArray = new ListViewModelArray()
}
V2
@ObservedV2
export default class ListViewModel {
@Trace lists: Array = []
}
```
🌸🌼🌺
更多推荐


所有评论(0)