MVVM vs MVI 架构对比
一、核心概念
MVVM(Model-View-ViewModel)
┌───────────┐ 观察/绑定 ┌─────────────┐ 请求数据 ┌───────────┐
│ │ ◄──────────────── │ │ ────────────► │ │
│ View │ │ ViewModel │ │ Model │
│ │ ────事件调用────► │ │ ◄──返回数据── │ │
└───────────┘ └─────────────┘ └───────────┘
- View:UI 层,观察 ViewModel 的数据变化
- ViewModel:持有 UI 状态(多个 LiveData/StateFlow),处理业务逻辑
- Model:数据源(Repository、网络、数据库等)
MVI(Model-View-Intent)
┌───────────┐ ┌─────────────┐ ┌───────────┐
│ │ ──── Intent ────► │ │ ────请求────► │ │
│ View │ │ ViewModel │ │ Model │
│ │ ◄── State ───── │ (Reducer) │ ◄──数据────── │ │
└───────────┘ └─────────────┘ └───────────┘
▲ │
└────────── 单一状态流 ───────────┘
Intent → Reducer → NewState → Render(单向数据流)
- Intent:用户意图(事件),用密封类统一描述
- Model:这里特指不可变的 UI State(整个页面的状态快照)
- View:根据唯一 State 渲染 UI
二、代码对比(Android / Kotlin)
MVVM 典型实现
// ── ViewModel ──
class UserViewModel(private val repo: UserRepository) : ViewModel() {
// ⚠️ 多个独立的状态流
private val _userName = MutableStateFlow("")
val userName: StateFlow<String> = _userName
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading
private val _error = MutableSharedFlow<String>()
val error: SharedFlow<String> = _error
fun loadUser(id: String) {
viewModelScope.launch {
_isLoading.value = true
try {
val user = repo.getUser(id)
_userName.value = user.name
} catch (e: Exception) {
_error.emit(e.message ?: "Unknown error")
} finally {
_isLoading.value = false
}
}
}
fun updateName(name: String) {
_userName.value = name
}
}
// ── View (Activity/Fragment) ──
class UserFragment : Fragment() {
private val vm: UserViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// 分别观察多个状态
lifecycleScope.launch { vm.userName.collect { tvName.text = it } }
lifecycleScope.launch { vm.isLoading.collect { progressBar.isVisible = it } }
lifecycleScope.launch { vm.error.collect { showToast(it) } }
btnLoad.setOnClickListener { vm.loadUser("123") }
}
}
MVI 典型实现
// ── 契约:统一定义 Intent、State、Effect ──
// Intent(用户意图)
sealed class UserIntent {
data class LoadUser(val id: String) : UserIntent()
data class UpdateName(val name: String) : UserIntent()
}
// State(不可变的唯一 UI 状态)
data class UserState(
val userName: String = "",
val isLoading: Boolean = false,
)
// Side Effect(一次性事件)
sealed class UserEffect {
data class ShowError(val message: String) : UserEffect()
}
// ── ViewModel ──
class UserViewModel(private val repo: UserRepository) : ViewModel() {
// ✅ 唯一状态源
private val _state = MutableStateFlow(UserState())
val state: StateFlow<UserState> = _state
private val _effect = Channel<UserEffect>()
val effect: Flow<UserEffect> = _effect.receiveAsFlow()
// ✅ 统一入口处理 Intent
fun handleIntent(intent: UserIntent) {
when (intent) {
is UserIntent.LoadUser -> loadUser(intent.id)
is UserIntent.UpdateName -> reduce { copy(userName = intent.name) }
}
}
private fun loadUser(id: String) {
viewModelScope.launch {
reduce { copy(isLoading = true) }
try {
val user = repo.getUser(id)
reduce { copy(userName = user.name, isLoading = false) }
} catch (e: Exception) {
reduce { copy(isLoading = false) }
_effect.send(UserEffect.ShowError(e.message ?: "Unknown"))
}
}
}
// 线程安全的状态更新
private fun reduce(block: UserState.() -> UserState) {
_state.update { it.block() }
}
}
// ── View ──
class UserFragment : Fragment() {
private val vm: UserViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// ✅ 只需观察一个状态
lifecycleScope.launch {
vm.state.collect { state ->
tvName.text = state.userName
progressBar.isVisible = state.isLoading
}
}
// 一次性事件
lifecycleScope.launch {
vm.effect.collect { effect ->
when (effect) {
is UserEffect.ShowError -> showToast(effect.message)
}
}
}
btnLoad.setOnClickListener {
vm.handleIntent(UserIntent.LoadUser("123"))
}
}
}
三、关键差异总结
| 维度 | MVVM | MVI |
|---|---|---|
| 数据流向 | 双向 / 部分单向 | 严格单向(Intent → State → UI) |
| 状态管理 | 多个分散的 LiveData/StateFlow | 单一不可变 State(Single Source of Truth) |
| 用户事件 | View 直接调用 ViewModel 方法 | 通过 sealed class Intent 统一派发 |
| 状态一致性 | ⚠️ 多状态可能不同步 | ✅ 原子更新,不会出现状态不一致 |
| 可追踪/可调试 | 较难(状态分散) | ✅ 每次状态变更有迹可循(类似 Redux) |
| 模板代码 | 较少 | 较多(需定义 Intent、State、Effect) |
| 学习曲线 | 较低 | 较高 |
| 适合场景 | 中小页面、状态简单 | 复杂页面、多状态交互、需要时间旅行调试 |
四、状态一致性问题示例
这是 MVVM 最常见的痛点:
// MVVM 中的潜在问题
_isLoading.value = false
// ⬇ 此处如果发生线程切换,UI 可能在 isLoading=false 但 data 还没更新时渲染
_data.value = newData
// MVI 中不存在此问题 —— 原子更新
reduce { copy(isLoading = false, data = newData) }
五、架构演进关系
MVC ──► MVP ──► MVVM ──► MVI
│ │
多个可观察状态 单一不可变状态
方法调用 Intent 驱动
单向数据流
MVI 可以看作 MVVM + 单向数据流 + 状态集中管理 的增强版。
六、选型建议
项目复杂度低 / 快速开发 ──► MVVM(够用且高效)
页面状态多且相互关联 ──► MVI(状态一致性好)
需要精确的状态追踪与调试 ──► MVI
团队已有 Redux/Flux 经验 ──► MVI(思想相通)
实际工程中:可以混合使用
• 简单页面用 MVVM
• 复杂业务流用 MVI
一句话总结:
MVVM 解决了 View 与 Model 的解耦问题;MVI 在此基础上进一步解决了 状态一致性 和 数据流方向混乱 的问题,代价是更多的模板代码和更高的学习成本。