MVI模式是Model-View-Intent(模型-视图-意图)的简称。是在移动应用开发常见的一种软件架构模式,如图1所示。其中:

模型(Model)
表示应用的状态。通常是一个不可变的数据类。
视图(View)
用户界面,用于显示状态并发送用户动作。
意图(Intent)
表示用户意图(或事件),这些意图会触发状态更新。
在HarmonyOS Next中,可以使用ArkTS进行开发基于MVI架构的移动应用。下面将通过展示一个简单生成密码应用来了解MVI架构。
一、定义状态(数据模型)
在简易密码生成的应用中,需要展示原始字符串数据和将原始字符串生成密码的数据。因此,在移动应用状态的定义中需要包含这两种属性。定义如下状态数据类CryptState的代码如下:
export class CryptState{
//原始数据
content:string = ""
//加密数据
crypt:string = ""
constructor(content:string) {
this.content = content
}
}
因为将原始数据加密生成加密的数据,所以在构造方法中,只传递了一个元素数据content的属性值。
二、定义意图(Intent)
在密码生成的应用中,需要完成意图非常简单,只有两种:一种是生成密码的意图,另外一种是重置数据的意图。基于此,定义一个意图接口CryptIntent,代码如下:
export interface CryptIntent{
type:"GenerateCrypt"|"ResetCrypt"
}
在CryptIntent中,用:
(1). “GenerateCrypt”:表示生成密码意图;
(2). “ResetCrypt”:表示重置原始数据为空的意图;
三、创建ViewModel来处理意图并管理状态
ViewModel也是MVI架构的重要成员,它作为UI层(View)与业务逻辑层(Model)之间的桥梁,负责处理用户意图(Intent)并管理应用状态(State)。通过处理不同的意图,使得应用的状态也发生相应的变化。在密码生成的实例中,定义CryptViewModel类来实现这个桥梁的作用,通过处理不同的意图,达到修改状态的目的。
import { CryptState } from "../model/CryptState"
import { cryptoFramework } from "@kit.CryptoArchitectureKit"
import {CryptIntent} from "../intent/CryptIntent"
@Observed
export class CryptViewModel{
private chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
@Track state:CryptState = new CryptState("")
processIntent(intent:CryptIntent){
let newState = new CryptState(this.state.content)
switch(intent.type){
case "GenerateCrypt":{
this.generate(newState)
break;
}
case "ResetCrypt":{
this.reset(newState)
break;
}
}
this.state = newState
}
/**
* 使用安全随机数生成密码
* @param content
* @returns
*/
async generate(state:CryptState){
//定义生成的密码串
let result = ""
//生成随机数
let rand = cryptoFramework.createRandom()
//生成随机字节数组
let randData = await rand.generateRandom(state.content.length)
let byteArr = new Uint8Array(randData.data.buffer)
//将字节映射到字符集
for(let i = 0; i<byteArr.length;i++){
result+=this.chars.charAt(byteArr[i]%this.chars.length)
}
state.crypt = result
}
reset(state:CryptState){
state.content = ""
state.crypt = ""
}
}
在上述代码中,有几点说明:
(1)@Observed修饰器来修饰CryptViewModel类,这表示标记该类是可观察的;
(2)@Track装饰器用于标记类中的属性,当该属性被修改时,会触发UI更新。@Track往往和@Observed配合使用;
(3)在CryptViewModel类的processIntent方法的定义中,首先创建新的状态值newState,然后处理不同的意图,是的新的状态值进行变更,然后再将@Track追踪的应用状态值state变更为newState;
(2)在CryptViewModel类的generate方法定义成一个包含CryptState参数的异步方法。这是因为在生成密码中调用的rand.generateRandom(state.content.length)方法返回一个Promise对象,必须在异步方法中调用。对于该方法可以通过await用于等待一个 Promise 完成,实现异步调用。因此将该类的generate方法定义为async异步方法。在generate方法中,最后生成的密文赋值给状态state对象的crypt属性。
(3)reset方法,也是接受一个状态值对象,将状态的两个属性值content和crypt分别重置为空字符串。
四、定义界面
4.1 定义文本输入的UI自定义控件 TextInput
因为界面中需要通过不同的输入,进行处理,因此自定义一个TextInput的UI控件实现原始数据的输入,代码如下:
import { CryptViewModel } from "../vm/CryptViewModel"
@Component
export struct InputTxt{
@ObjectLink viewModel:CryptViewModel
build(){
Row(){
TextInput({placeholder:"输入字符串",text:this.viewModel.state.content})
.onChange((e)=>{
this.viewModel.state.content = e
})
}.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Center)
.width("80%")
.height(LayoutPolicy.wrapContent)
.border({width:1,color:Color.Blue})
}
}
在上述代码中,使用@ObjectLink装饰器修饰viewModel对象。在这里,通过@ObjectLink实现嵌套对象双向数据绑定,必须与 @Observed 配合使用。在主界面Index的CryptViewModel对象的任何属性的变化,与InputTxt组件同步修改。
4.2 定义主页面
主页面的代码如下:
import { CryptViewModel } from '../vm/CryptViewModel'
import {CryptIntent} from "../intent/CryptIntent"
import { InputTxt } from './InputText'
@Entry
@Component
@Component
struct Index {
@State viewModel:CryptViewModel = new CryptViewModel()
build() {
Column() {
Text("密码生成应用")
.fontSize(40)
.fontColor(Color.Green)
.margin({bottom:30})
InputTxt({viewModel:this.viewModel})
Text(this.viewModel.state.crypt)
.fontSize(28)
.fontColor(Color.Black)
.margin({bottom:20})
Row(){
Button("生成密码串")
.onClick(()=>{
this.viewModel.processIntent({type:"GenerateCrypt"})
})
.margin({right:20})
Button("重置输入")
.onClick(()=>{
this.viewModel.processIntent({type:"ResetCrypt"})
})
.margin({left:20})
}.width("100%")
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Center)
}.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.height('100%')
.width('100%')
}
}
最后的运行结果如下图所示:

1580

被折叠的 条评论
为什么被折叠?



