Android MVI 中 setState(reduce: State.() -> State) 设计说明文档


Android MVI 中 setState(reduce: State.() -> State) 设计说明文档

1. 背景与设计目标

在 Android MVI(Model--View--Intent)架构中,State 是不可变的 ,UI 的任何变化都必须通过 State 的演进(reduction) 来完成。

本设计的核心目标是:

  • 保证 State 更新是原子性的
  • 确保 新 State 一定基于旧 State
  • 避免 UI 状态被意外重置
  • 让状态更新逻辑 集中、可预测、可追踪

2. 核心代码

kotlin 复制代码
protected fun setState(reduce: State.() -> State) {
    val newState = currentState.reduce()
    newState.toString().iLog(TAG)
    currentState = newState
    _uiState.value = currentState
}

3. 为什么 setState 要接收 Lambda?

3.1 reduce 不是 State,而是「状态演进规则」

kotlin 复制代码
State.() -> State

表示:

给我一个旧 State,我返回一个新 State

这不是一个值,而是一段 "如何从旧状态计算新状态的逻辑"


3.2 为什么不能设计成这样?(❌ 错误)

kotlin 复制代码
protected fun setState(state: State)
问题:
  1. ❌ 无法保证 state 基于最新的 currentState
  2. ❌ 状态可能在外部被提前计算,存在并发风险
  3. ❌ reducer 逻辑分散,破坏 MVI 的一致性
  4. ❌ 无法保证 State 的"演进关系"

3.3 正确模型(✅)

text 复制代码
NewState = reduce(OldState)

而不是:

text 复制代码
NewState = 某个外部算好的 State

4. Lambda 什么时候执行?(关键理解点)

调用代码:

kotlin 复制代码
setState {
    copy(loadState = LoadState.Loading)
}

执行时序:

  1. { copy(...) }

    👉 只是创建了一个 Lambda(函数值),不会执行

  2. 进入 setState

  3. 执行这一行:

kotlin 复制代码
val newState = currentState.reduce()

👉 此时 Lambda 才真正执行


核心规则(必须记住)

Lambda 在"被传递时不会执行",
只有在调用 () 时才会执行


5. 为什么 reducer 里几乎总是用 copy

5.1 State 的正确形态

kotlin 复制代码
data class XxxState(
    val loadState: LoadState = LoadState.Idle,
    val loadMoreState: LoadMoreState = LoadMoreState.Idle,
    val data: Data? = null
)

State 必须是:

  • data class
  • 不可变(val)
  • 可整体替换

5.2 copy 的作用

kotlin 复制代码
copy(loadState = LoadState.Loading)

含义是:

在旧 State 基础上,只修改指定字段,其余字段保持不变

等价于手写:

kotlin 复制代码
XxxState(
    loadState = LoadState.Loading,
    loadMoreState = this.loadMoreState,
    data = this.data
)

5.3 为什么不能 new 一个 State?(❌)

kotlin 复制代码
setState {
    XxxState(loadState = LoadState.Loading)
}
问题:
  1. ❌ 其他字段会被重置为默认值 / null
  2. ❌ 破坏 State 的连续性
  3. ❌ UI 会出现"莫名其妙回退 / 闪烁"的 bug
  4. ❌ reducer 语义被破坏(不再基于旧状态)

6. 为什么 State 常用「接口 + data class」?

kotlin 复制代码
interface UiState

interface XxxState : UiState {
    val loadState: LoadState
    val data: Data?
}

data class XxxStateImpl(
    override val loadState: LoadState = LoadState.Idle,
    override val data: Data? = null
) : XxxState

好处:

  • View 层依赖接口(解耦)
  • 内部实现可演进
  • reducer 中始终操作具体 data class(支持 copy

7. 正确使用方式(推荐写法)

✅ 标准用法

kotlin 复制代码
setState {
    copy(loadState = LoadState.Loading)
}
kotlin 复制代码
setState {
    copy(
        loadState = LoadState.LoadSuccess,
        data = result
    )
}

❌ 错误用法(禁止)

kotlin 复制代码
setState {
    XxxState(...)
}
kotlin 复制代码
setState(
    currentState.copy(...)
)

8. 注意事项(非常重要)

8.1 reducer 中不要有副作用

❌ 不要:

  • 发请求
  • 写数据库
  • 打 Toast
  • 启动协程

✅ reducer 只负责计算新 State


8.2 reducer 必须是"纯函数"

同样的输入 State,必须得到同样的输出 State。


8.3 不要在 reducer 外部修改 State

❌:

kotlin 复制代码
currentState = currentState.copy(...)

✅:

kotlin 复制代码
setState { copy(...) }

9. 一句话总结(可以放在文档最前面)

**setState 接收的不是 State,

而是"如何从旧 State 生成新 State 的规则"。

copy 是 MVI 中 State 演进的唯一正确方式。**


相关推荐
恋猫de小郭5 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab6 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe11 小时前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农19 小时前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少19 小时前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker19 小时前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋20 小时前
Android 协程时代,Handler 应该退休了吗?
android
火柴就是我1 天前
让我们实现一个更好看的内部阴影按钮
android·flutter
砖厂小工2 天前
用 GLM + OpenClaw 打造你的 AI PR Review Agent — 让龙虾帮你审代码
android·github
张拭心2 天前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能