聊聊MVVM与MVI

MVVM vs MVI 架构对比

一、核心概念

MVVM(Model-View-ViewModel)

sql 复制代码
┌───────────┐     观察/绑定      ┌─────────────┐    请求数据    ┌───────────┐
│           │ ◄──────────────── │             │ ────────────► │           │
│   View    │                   │  ViewModel  │               │   Model   │
│           │ ────事件调用────►  │             │ ◄──返回数据── │           │
└───────────┘                   └─────────────┘               └───────────┘
  • View:UI 层,观察 ViewModel 的数据变化
  • ViewModel:持有 UI 状态(多个 LiveData/StateFlow),处理业务逻辑
  • Model:数据源(Repository、网络、数据库等)

MVI(Model-View-Intent)

markdown 复制代码
┌───────────┐                   ┌─────────────┐               ┌───────────┐
│           │ ──── Intent ────► │             │ ────请求────► │           │
│   View    │                   │  ViewModel  │               │   Model   │
│           │ ◄── State ─────  │  (Reducer)  │ ◄──数据────── │           │
└───────────┘                   └─────────────┘               └───────────┘
      ▲                                │
      └────────── 单一状态流 ───────────┘

        Intent → Reducer → NewState → Render(单向数据流)
  • Intent:用户意图(事件),用密封类统一描述
  • Model :这里特指不可变的 UI State(整个页面的状态快照)
  • View:根据唯一 State 渲染 UI

二、代码对比(Android / Kotlin)

MVVM 典型实现

kotlin 复制代码
// ── 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 典型实现

kotlin 复制代码
// ── 契约:统一定义 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 最常见的痛点:

kotlin 复制代码
// MVVM 中的潜在问题
_isLoading.value = false
// ⬇ 此处如果发生线程切换,UI 可能在 isLoading=false 但 data 还没更新时渲染
_data.value = newData

// MVI 中不存在此问题 ------ 原子更新
reduce { copy(isLoading = false, data = newData) }

五、架构演进关系

markdown 复制代码
MVC  ──►  MVP  ──►  MVVM  ──►  MVI
                      │          │
               多个可观察状态   单一不可变状态
               方法调用        Intent 驱动
                              单向数据流

MVI 可以看作 MVVM + 单向数据流 + 状态集中管理 的增强版。


六、选型建议

markdown 复制代码
项目复杂度低 / 快速开发        ──► MVVM(够用且高效)
                                  
页面状态多且相互关联           ──► MVI(状态一致性好)
需要精确的状态追踪与调试       ──► MVI
团队已有 Redux/Flux 经验       ──► MVI(思想相通)

实际工程中:可以混合使用
  • 简单页面用 MVVM
  • 复杂业务流用 MVI

一句话总结:

MVVM 解决了 View 与 Model 的解耦问题;MVI 在此基础上进一步解决了 状态一致性数据流方向混乱 的问题,代价是更多的模板代码和更高的学习成本。

相关推荐
千里马学框架24 分钟前
aosp新增窗口层级 Type 完整实现方案(有源码)-wms需求和面试题
android·智能手机·架构·wms·aaos·车机
AI_Auto2 小时前
【智能制造】- APS系列|14 生产计划三层架构:长期、中期、短期
架构·制造
morning_judger2 小时前
Agent系列(一) - Agent系统分层架构
人工智能·架构
跨境数据猎手2 小时前
Superbuy淘宝代购集运系统架构拆解,复刻方案参考
爬虫·架构·系统架构
Rain5093 小时前
mini-cc 的 MCP 协议:给 AI 装个 USB-C 接口
c语言·开发语言·前端·人工智能·架构·node.js·ai编程
雨辰AI4 小时前
SpringBoot3 整合达梦 DM9 超详细入门实战|从零搭建可直接上线
数据库·微服务·架构·政务
辰海Coding4 小时前
MiniSpring框架学习-分解 Dispatcher
java·学习·spring·架构
wb043072015 小时前
厨房装监控——从阿明餐厅的“出餐慢“投诉,看可观测性的三大支柱
架构
喵个咪6 小时前
选择第三方IAM还是自建权限体系?中小型后台系统权限架构决策指南
后端·架构·go
ting94520006 小时前
Ava 2.0 技术架构与核心能力深度解析:自主式 AI BDR 的全链路技术实现
人工智能·架构