这个问题问得非常到位 ,而且现在正是你该把这件事**"一次性讲清楚"的时候了 👍
你已经学了 State、重组、生命周期、SideEffect / LaunchedEffect / DisposableEffect,如果不把「Effect 是什么」抽象成一个统一概念**,后面一定会越学越乱。
我给你一个工程级、可在脑子里复用的定义。
一、先给你一句话定义(核心)
Compose 中的 Effect(副作用)=
在 UI 声明(Composable 函数)之外,对"外部世界"产生影响的操作。
关键词:
- 不只是返回 UI
- 会改变 UI 之外的状态
- 不能被安全地重复执行
二、为什么 Compose 一定要"显式 Effect"?(本质原因)
因为 Compose 的核心模型是:
kotlin
UI = f(State)
而 Composable 函数有三个特点:
- 会被反复执行(重组)
- 执行顺序不稳定
- 执行结果可能被丢弃
👉 所以:
❗任何"有副作用的代码",都不能直接写在 Composable 函数体里
三、什么算"副作用"?(你可以直接用这个判断)
属于 Effect 的事情:
- 启动协程
- 请求网络
- 订阅 Flow
- 打日志(某些情况)
- 修改系统 UI(状态栏、亮度)
- 注册监听 / Receiver
- 读写文件
- 调用 ViewModel 的业务方法(有条件)
不属于 Effect 的事情:
- if / when
- UI 计算
- 纯数据转换
derivedStateOf
四、Compose 是怎么"管副作用"的?
Compose 提供了一整套 Effect API,统一规则是:
Effect 只在"成功的重组"之后执行,并且与重组生命周期绑定
五、Effect 家族一览(你已经见过,但现在要统一理解)
| Effect API | 解决的问题 |
|---|---|
LaunchedEffect |
启动协程副作用 |
DisposableEffect |
有生命周期的副作用 |
SideEffect |
同步副作用 |
produceState |
异步 → State |
rememberUpdatedState |
解决闭包过期 |
snapshotFlow |
State → Flow |
👉 它们不是零散 API,而是一个体系。
六、Effect 的"统一执行模型"(非常重要)
所有 Effect 都遵守这条规则:
text
Composable 执行(可能多次)
↓
如果本次重组被提交
↓
Effect 执行
👉 Effect 永远不会在"半途"执行
七、用一句"工程级心智模型"记住 Effect
Composable 描述 UI,
Effect 让 UI 影响世界。
八、结合 MVI 的真实定位(你关心的)
在 MVI + Compose 中:
- Intent → 改变 State(VM)
- State → 驱动 UI(Composable)
- Effect → 连接 UI 与外部系统
示例(标准写法)
kotlin
@Composable
fun HomeScreen(vm: HomeViewModel) {
val uiState by vm.uiState.collectAsState()
LaunchedEffect(Unit) {
vm.sendIntent(Intent.Load)
}
if (uiState.error != null) {
SideEffect {
showToast(uiState.error)
}
}
}
九、一个非常重要的误区(一定要避开)
❌ 错误理解
"Effect 就是生命周期回调"
✅ 正确理解
Effect 是"声明式生命周期"下的副作用管理工具
十、你现在已经形成一个「完整闭环」了
你已经掌握了:
- State → UI
- State → Recomposition
- Recomposition → Effect
- Effect → 外部世界
👉 这就是 Compose 的完整运行模型
最后送你一句话(建议你记住)
如果一段代码:
离开这个 Composable 还能存在,
那它就必须写在 Effect 里。